Skip to main content
Fix issues with API keys, permissions, and webhook signature verification.

Common Errors

401 Unauthorized

This error means the API could not authenticate your request. You will receive an HTTP 401 response. Error:
{
  "message": "Invalid or missing API key",
  "error_code": "unauthorized"
}
Causes & Solutions:
Every request must include the Authorization header with a valid Bearer token:
curl https://app.lettr.com/api/domains \
  -H "Authorization: Bearer lttr_xxxxxxxxxxxx"
If the header is missing entirely, the API will reject the request with a 401 status.
All Lettr API keys start with the le_ prefix. Double-check for:
  • Typos or extra whitespace in the key
  • A missing or incorrect Bearer prefix in the header value
  • Quotes or escape characters accidentally included in the key string
If an API key has been deleted from the dashboard, it immediately stops working. Go to Settings > API Keys in the Lettr dashboard to confirm your key still exists and create a new one if needed.

403 Forbidden

A 403 response means your key was recognized but is not allowed to perform the requested action. There are two main causes.

Insufficient Permissions

Error:
{
  "message": "API key lacks required permission for this operation",
  "error_code": "forbidden"
}
Your API key has the Sending Only permission level but you are trying to access an endpoint that requires Full Access (for example, managing domains or reading analytics). Solution: Create a new API key with Full Access permissions in Settings > API Keys, or use an existing key that already has the required permission level.

IP Address Restriction

Error:
{
  "message": "IP address not allowed for this API key",
  "error_code": "forbidden"
}
The API key has IP restrictions configured and your request is coming from an IP address that is not on the allow list. Solution:
  1. Go to Settings > API Keys in the dashboard
  2. Find the key and check its IP restriction settings
  3. Add your server’s IP address to the allow list, or remove the restriction if it is no longer needed
If you are unsure which IP your server uses, you can run curl ifconfig.me on the machine making the API requests.

Verifying Your API Key

Run a simple test request to confirm your key is valid and working:
curl -i https://app.lettr.com/api/domains \
  -H "Authorization: Bearer lttr_xxxxxxxxxxxx"
If the key is valid, you will receive an HTTP 200 response with your domain data. If the key is invalid or missing, you will receive an HTTP 401 response:
{
  "message": "Invalid or missing API key",
  "error_code": "unauthorized"
}
The -i flag includes response headers in the output, which is helpful for debugging.

Correct Authentication Usage

All requests to the Lettr API must include the Authorization header with a Bearer token. GET request:
curl https://app.lettr.com/api/domains \
  -H "Authorization: Bearer lttr_xxxxxxxxxxxx"
POST request:
curl -X POST https://app.lettr.com/api/emails \
  -H "Authorization: Bearer lttr_xxxxxxxxxxxx" \
  -H "Content-Type: application/json" \
  -d '{
    "from": "you@yourdomain.com",
    "to": ["recipient@example.com"],
    "subject": "Hello from Lettr",
    "html": "<p>This is a test email.</p>"
  }'
Do not pass your API key as a query parameter or in the request body. Always use the Authorization: Bearer header.

Environment Variables

Never hard-code API keys in your source code. Store them in environment variables instead. Using a .env file:
# .env
LETTR_API_KEY=lttr_xxxxxxxxxxxx
Exporting in your shell:
export LETTR_API_KEY=lttr_xxxxxxxxxxxx
Using the variable in a curl request:
curl https://app.lettr.com/api/domains \
  -H "Authorization: Bearer $LETTR_API_KEY"

Webhook Signature Verification Issues

When receiving webhooks from Lettr, you should verify the lettr-signature header to confirm the request is authentic. Below are common issues and how to solve them.

Use the Raw Request Body

Signature verification must be performed against the raw, unparsed request body. If your framework parses the body as JSON before you verify the signature, the comparison will fail. Express.js — correct setup:
const express = require("express");
const crypto = require("crypto");
const app = express();

// Use express.raw() so the body is a Buffer, not parsed JSON
app.post(
  "/webhooks/lettr",
  express.raw({ type: "application/json" }),
  (req, res) => {
    const signature = req.headers["lettr-signature"];
    const webhookSecret = process.env.LETTR_WEBHOOK_SECRET;

    const expectedSignature = crypto
      .createHmac("sha256", webhookSecret)
      .update(req.body)
      .digest("hex");

    if (signature !== expectedSignature) {
      console.error("Webhook signature verification failed");
      return res.status(401).send("Invalid signature");
    }

    const event = JSON.parse(req.body);
    console.log("Verified webhook event:", event);

    res.status(200).send("OK");
  }
);

Common Pitfalls

The signature header is lettr-signature. Make sure you are not looking for a different header name such as x-signature or x-lettr-signature.
If you apply express.json() globally before your webhook route, the body will be a parsed object and signature verification will fail. Use express.raw() specifically on the webhook route as shown above.
Each webhook endpoint in the dashboard has its own secret. Make sure the LETTR_WEBHOOK_SECRET environment variable matches the secret shown for the specific endpoint you configured.

Key Security Best Practices

Add .env to your .gitignore file. If a key is accidentally committed, revoke it immediately in the dashboard and create a new one.
Create separate API keys for development, staging, and production environments. This limits the impact if any single key is compromised.
Create a new key, update your environment to use it, then delete the old key. Aim to rotate keys at least every 90 days.
If your application only sends emails, use a key with Sending Only permissions. Only use Full Access keys when you need to manage domains, templates, or other account resources.