> ## Documentation Index
> Fetch the complete documentation index at: https://docs.lettr.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Authentication Issues

> Fix 401 errors, invalid or missing API keys, permission problems, and webhook signature verification failures with the Lettr API

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:**

```json theme={null}
{
  "message": "Invalid or missing API key",
  "error_code": "unauthorized"
}
```

**Causes & Solutions:**

<AccordionGroup>
  <Accordion title="Missing API Key">
    Every request must include the `Authorization` header with a valid Bearer token:

    ```bash theme={null}
    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.
  </Accordion>

  <Accordion title="Invalid Key Format">
    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
  </Accordion>

  <Accordion title="Revoked or Deleted Key">
    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.
  </Accordion>
</AccordionGroup>

***

### 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:**

```json theme={null}
{
  "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:**

```json theme={null}
{
  "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

<Tip>
  If you are unsure which IP your server uses, you can run `curl ifconfig.me` on the machine making the API requests.
</Tip>

***

## Verifying Your API Key

Run a simple test request to confirm your key is valid and working:

```bash theme={null}
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:

```json theme={null}
{
  "message": "Invalid or missing API key",
  "error_code": "unauthorized"
}
```

<Note>
  The `-i` flag includes response headers in the output, which is helpful for debugging.
</Note>

***

## Correct Authentication Usage

All requests to the Lettr API must include the `Authorization` header with a Bearer token.

**GET request:**

```bash theme={null}
curl https://app.lettr.com/api/domains \
  -H "Authorization: Bearer lttr_xxxxxxxxxxxx"
```

**POST request:**

```bash theme={null}
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>"
  }'
```

<Warning>
  Do not pass your API key as a query parameter or in the request body. Always use the `Authorization: Bearer` header.
</Warning>

***

## Environment Variables

Never hard-code API keys in your source code. Store them in environment variables instead.

**Using a `.env` file:**

```bash theme={null}
# .env
LETTR_API_KEY=lttr_xxxxxxxxxxxx
```

**Exporting in your shell:**

```bash theme={null}
export LETTR_API_KEY=lttr_xxxxxxxxxxxx
```

**Using the variable in a curl request:**

```bash theme={null}
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:**

```javascript theme={null}
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

<AccordionGroup>
  <Accordion title="Wrong header name">
    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`.
  </Accordion>

  <Accordion title="Body already parsed as JSON">
    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.
  </Accordion>

  <Accordion title="Wrong webhook secret">
    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.
  </Accordion>
</AccordionGroup>

***

## Key Security Best Practices

<AccordionGroup>
  <Accordion title="Never commit keys to git">
    Add `.env` to your `.gitignore` file. If a key is accidentally committed, revoke it immediately in the dashboard and create a new one.
  </Accordion>

  <Accordion title="Use environment-specific keys">
    Create separate API keys for development, staging, and production environments. This limits the impact if any single key is compromised.
  </Accordion>

  <Accordion title="Rotate keys regularly">
    Create a new key, update your environment to use it, then delete the old key. Aim to rotate keys at least every 90 days.
  </Accordion>

  <Accordion title="Use minimum permissions">
    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.
  </Accordion>
</AccordionGroup>

***

## Related Topics

<CardGroup cols={3}>
  <Card title="API Keys" icon="key" href="/learn/api-keys/introduction">
    Learn how to create and manage your API keys.
  </Card>

  <Card title="Permissions" icon="shield-halved" href="/learn/api-keys/permissions">
    Understand the difference between Full Access and Sending Only keys.
  </Card>

  <Card title="Webhook Authorization" icon="lock" href="/learn/webhooks/authorization">
    Set up and verify webhook signatures.
  </Card>
</CardGroup>
