Faivelo
Dashboard
Transactional Email

Webhooks

Get delivery, bounce, and complaint events pushed to your server.

Webhooks are the push counterpart of polling a send's status: register an HTTPS endpoint and Faivelo POSTs delivery events to it as they happen. Use them to mark a message delivered in your app, alert on bounces, or clean bad addresses out of your database the moment they bounce.

Manage webhooks in the dashboard under Settings → Developers → Webhooks. They are included on the Growth, Pro and Business plans.

This page covers the delivery events for transactional email. Webhooks also cover mailboxes, domains and account health, with retries, a delivery log and secret rotation: see the full Webhooks guide.

Events

EventFires when
email.deliveredThe receiving server accepted the message
email.bouncedThe receiving server rejected it (hard bounce; the address is suppressed)
email.complainedThe recipient marked it as spam (the address is suppressed)

Each webhook subscribes to the events you choose.

The payload

Every delivery is a JSON POST:

{
  "type": "email.bounced",
  "created_at": "2026-08-19T14:03:22.000Z",
  "data": {
    "email_id": "cme1x…",
    "from": "receipts@yourdomain.com",
    "to": ["customer@example.com"],
    "subject": "Your order is confirmed",
    "template_alias": "order-confirmation",
    "status": "BOUNCED",
    "sent_at": "2026-08-19T14:03:20.000Z"
  }
}

data.email_id is the id the send call returned. Bounce and complaint events may carry extra detail about the reason. The request also carries an X-Faivelo-Event header naming the event type.

Verifying signatures

Anyone who discovers your endpoint URL can POST fake JSON to it — so verify every request. Each webhook has a signing secret (shown when you create it), and every delivery is signed with an X-Faivelo-Signature header:

X-Faivelo-Signature: t=1755612202,v1=5257a869e7…

v1 is HMAC-SHA256(secret, "{t}.{raw request body}"). To verify:

import { createHmac, timingSafeEqual } from 'node:crypto'

function verify(rawBody, header, secret) {
  const { t, v1 } = Object.fromEntries(header.split(',').map(p => p.split('=')))
  // Reject stale timestamps to block replay attacks
  if (Math.abs(Date.now() / 1000 - Number(t)) > 300) return false
  const expected = createHmac('sha256', secret).update(`${t}.${rawBody}`).digest('hex')
  return v1.length === expected.length
    && timingSafeEqual(Buffer.from(v1), Buffer.from(expected))
}

Compute the HMAC over the raw request body, before any JSON parsing — a re-serialized body won't match.

Delivery behavior

  • Your endpoint has 5 seconds to respond with a 2xx status. Respond first, process after — do your database work asynchronously.
  • Redirects are not followed; point the webhook at its final URL.
  • The dashboard shows each webhook's last successful delivery and last error, so a misbehaving endpoint is visible at a glance.
  • Each event is delivered once — there is no automatic retry queue. Treat webhooks as the fast path and the send status API as the source of truth you can re-query anytime.

Testing

Every webhook has a Send test button in the dashboard. It delivers a real signed email.delivered event with "test": true in the payload — so you can prove your endpoint and signature verification end-to-end without sending an email.