Docs menu

Webhooks

Flinkk Mail calls your endpoint when something happens to an email you sent. Each delivery is signed, retried on failure and listed in your delivery log.

Events

EventWhen
email.sentAn email was accepted for sending.
email.deliveredThe recipient's mail server accepted it.
email.bouncedIt bounced. bounceType is Permanent, Transient or Undetermined. Permanent bounces are also added to the suppression list.
email.complainedThe recipient marked it as spam (also suppressed automatically).
email.openedThe recipient opened it (each open is an event).
email.clickedThe recipient clicked a link (link has the URL).

The same endpoints can also receive agent events such as email.received and draft.pending_approval, see the agent inbox API.

Create an endpoint

curl -X POST https://mail.flinkk.io/api/v1/webhooks \
  -H "Authorization: Bearer $FLINKK_API_KEY" -H "Content-Type: application/json" \
  -d '{"url":"https://example.com/hooks/flinkk","events":["email.delivered","email.bounced","email.complained"]}'
# 201 { "id": "...", "secret": "whsec_..." }  the secret is shown once

The URL must be public https. You can also manage endpoints in the app under Agents, Webhooks. GET /api/v1/webhooks lists them, PATCH /api/v1/webhooks/{id} with {"enabled": false} pauses one, DELETE /api/v1/webhooks/{id} removes it, POST /api/v1/webhooks/{id}/test sends a signed ping, and GET /api/v1/webhooks/{id}/deliveries shows recent deliveries, attempts and errors.

What you receive

{
  "id": "evt_Hq3...",
  "type": "email.bounced",
  "createdAt": "2026-11-02T10:15:02.114Z",
  "data": {
    "emailId": "ckx9...",
    "from": "hello@yourdomain.com",
    "to": "gone@example.com",
    "subject": "Welcome aboard",
    "occurredAt": "2026-11-02T10:15:01.000Z",
    "bounceType": "Permanent",
    "bounceSubType": "General"
  }
}

Payloads carry ids and metadata, never the message body. Use emailId with GET /api/v1/emails/{id} for the full record and timeline.

Verify the signature

Each request has Flinkk-Signature: t=<unix>,v1=<hex>, the hex HMAC-SHA256 of t + "." + the raw request body keyed with your endpoint secret, plus Flinkk-Event and Flinkk-Delivery. Verify against the raw body, compare in constant time and reject old timestamps.

import { verifyWebhook } from 'flinkk-mail';

const ok = await verifyWebhook(rawBody, req.headers['flinkk-signature'], process.env.FLINKK_WEBHOOK_SECRET!);
if (!ok) return res.status(401).end();

Retries

Reply with any 2xx within 8 seconds. Otherwise Flinkk Mail retries after 1 minute, 5 minutes, 30 minutes, 2 hours, 6 hours and 24 hours, then marks the delivery failed. Deliveries can arrive more than once, so de-duplicate on id. Redirects are not followed.

On this page: Events · Create an endpoint · What you receive · Verify the signature · Retries