# Receive bounce webhooks from an email provider

`POST /api/v1/email/feedback/{provider}/{feedbackKey}`

- Authentication: Feedback key in the URL
- Billing: Free.
- Group: Feedback

The webhook target for an email provider's bounce, delivery and complaint notifications. Providers cannot send bearer tokens, so the account's feedback key rides in the URL (generate or rotate it on the dashboard's API keys page; rotating invalidates the URLs at once) and the provider name selects the payload adapter. The body is the provider's own payload, decoded as JSON regardless of content type because Amazon SNS posts JSON as `text/plain`. Payloads are read for their documented fields and nothing else; events that say nothing definitive about the address are ignored.

| Provider | Path | Mapping |
| --- | --- | --- |
| postmark | `/api/v1/email/feedback/postmark/{key}` | Bounce (hard types) → bounced · Delivery → delivered · SpamComplaint → complained. Soft bounces are ignored. |
| ses | `/api/v1/email/feedback/ses/{key}` | SNS notifications: Bounce with bounceType Permanent → bounced · Delivery → delivered · Complaint → complained. Subscription confirmations are answered. |
| mailgun | `/api/v1/email/feedback/mailgun/{key}` | delivered → delivered · failed with severity permanent → bounced · complained → complained. Temporary failures are ignored. |
| sendgrid | `/api/v1/email/feedback/sendgrid/{key}` | delivered → delivered · bounce and dropped → bounced · spamreport → complained. Deferred events are ignored. |

Throttled at 120 requests per minute per IP. Outcomes recorded here behave exactly like ones reported through `POST /api/v1/email/feedback`.

## Parameters

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `provider` | path | string | yes | Which provider's payload format to expect. One of: postmark, ses, mailgun, sendgrid. |
| `feedbackKey` | path | string | yes | The account's feedback key, which starts with `fb_`. |

## Request body

The provider's native webhook payload.

## Example request

```bash
curl -X POST https://spaw.co/api/v1/email/feedback/postmark/fb_… \
  -H "Content-Type: application/json" \
  -d '{
  "RecordType": "Bounce",
  "Type": "HardBounce",
  "Email": "mia@acme.com",
  "Description": "The server was unable to deliver your message (ex. unknown user, mailbox not found).",
  "BouncedAt": "2026-09-03T10:12:44Z"
}'
```

## Responses

### 200 — The outcomes the payload carried were recorded.

```json
{
    "success": true,
    "data": {
        "recorded": 1,
        "skipped": 0
    }
}
```

### 401 — The feedback key does not exist or was rotated.

```json
{
    "success": false,
    "error": {
        "code": "INVALID_FEEDBACK_KEY",
        "message": "That feedback key does not exist or was rotated.",
        "request_id": "req_01m1kgdm4xngzmbmff68g94w0c"
    }
}
```

### 404 — The provider path is not one of postmark, ses, mailgun, or sendgrid.

```json
{
    "success": false,
    "error": {
        "code": "UNKNOWN_FEEDBACK_PROVIDER",
        "message": "Supported providers: postmark, ses, mailgun, sendgrid.",
        "request_id": "req_01m1kgdm4xngzmbmff68g94w0c"
    }
}
```

### 429 — Over 5 requests per second for the key. Retry after the limit resets.

```json
{
    "success": false,
    "error": {
        "code": "RATE_LIMITED",
        "message": "Too many requests. Retry after the limit resets.",
        "request_id": "req_01m1kgdm4xngzmbmff68g94w0c"
    }
}
```

## Error codes

- `UNKNOWN_FEEDBACK_PROVIDER` — https://spaw.co/docs/errors/UNKNOWN_FEEDBACK_PROVIDER
- `INVALID_FEEDBACK_KEY` — https://spaw.co/docs/errors/INVALID_FEEDBACK_KEY
- `RATE_LIMITED` — https://spaw.co/docs/errors/RATE_LIMITED

---

Canonical page: https://spaw.co/docs/api/esp-bounce-webhook · OpenAPI document: https://spaw.co/openapi.json · All endpoints: https://spaw.co/docs/api
