API reference/feedback

Receive bounce webhooks from an email provider

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

authentication
Feedback key in the URL
billing
Free.

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 description
providerrequiredpathstringWhich provider's payload format to expect. One of: postmark, ses, mailgun, sendgrid.
feedbackKeyrequiredpathstringThe account's feedback key, which starts with `fb_`.

Request body

The provider's native webhook payload.

Example request

curl -X POST https://spaw.co/api/v1/email/feedback/postmark/fb_… \
  -H "Content-Type: application/json" \
  -d '{
  "RecordType": "Bounce",
  "Type": "HardBounce",
  "Email": "[email protected]",
  "Description": "The server was unable to deliver your message (ex. unknown user, mailbox not found).",
  "BouncedAt": "2026-09-03T10:12:44Z"
}'
const response = await fetch('https://spaw.co/api/v1/email/feedback/postmark/fb_…', {
  method: 'POST',
  headers: {
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({
    "RecordType": "Bounce",
    "Type": "HardBounce",
    "Email": "[email protected]",
    "Description": "The server was unable to deliver your message (ex. unknown user, mailbox not found).",
    "BouncedAt": "2026-09-03T10:12:44Z"
  }),
});
const result = await response.json();
import requests

response = requests.post(
    'https://spaw.co/api/v1/email/feedback/postmark/fb_…',
    json={
        'RecordType': 'Bounce',
        'Type': 'HardBounce',
        'Email': '[email protected]',
        'Description': 'The server was unable to deliver your message (ex. unknown user, mailbox not found).',
        'BouncedAt': '2026-09-03T10:12:44Z'
    },
)
result = response.json()
$ch = curl_init('https://spaw.co/api/v1/email/feedback/postmark/fb_…');
curl_setopt_array($ch, [
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_CUSTOMREQUEST => 'POST',
    CURLOPT_HTTPHEADER => ['Content-Type: application/json'],
    CURLOPT_POSTFIELDS => json_encode([
        'RecordType' => 'Bounce',
        'Type' => 'HardBounce',
        'Email' => '[email protected]',
        'Description' => 'The server was unable to deliver your message (ex. unknown user, mailbox not found).',
        'BouncedAt' => '2026-09-03T10:12:44Z'
    ]),
]);
$result = json_decode(curl_exec($ch), true);

Responses

200The outcomes the payload carried were recorded.

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

401The feedback key does not exist or was rotated.

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

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

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

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

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

Error codes

Failures answer { success: false, error: { code, message, request_id } }. Each code has its own page.

markdown version·openapi.json