Skip to content

Webhooks ​

A webhook delivers every domain event to an HTTPS endpoint you control.

Create ​

POST /v1/webhooks
bash
curl -X POST http://api.localhost/v1/webhooks \
  -H "X-API-Key: $NOTIFYZR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "url": "https://example.com/hooks/notifyzr" }'

url must be an absolute http(s) URL, up to 2,048 characters — anything else is 422 notifyzr.webhook.error.invalid_url.

The response includes a signing secret, shown once. Store it: it is how you verify a delivery came from Notifyzr.

List and delete ​

GET    /v1/webhooks
DELETE /v1/webhooks/{id}

There is no update and no per-event subscription — a webhook receives everything. Filter on X-Notifyzr-Event at your end.

What a delivery looks like ​

HeaderContents
X-Notifyzr-EventThe event type, e.g. contact.created
X-Notifyzr-SignatureHMAC-SHA256 of the raw body, hex-encoded

Verify against the raw bytes, before any JSON parsing, with a constant-time comparison:

php
$expected = hash_hmac('sha256', $rawBody, $secret);

if (!hash_equals($expected, $request->getHeaderLine('X-Notifyzr-Signature'))) {
    http_response_code(401);
    return;
}
js
const expected = crypto.createHmac('sha256', secret).update(rawBody).digest('hex')
const given = req.header('X-Notifyzr-Signature') ?? ''

if (expected.length !== given.length ||
    !crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(given))) {
  return res.sendStatus(401)
}

Events ​

Events are named resource.verb, and this is the whole catalog:

EventEmitted when
contact.created / contact.updated / contact.deletedA contact changes
contact.unsubscribed / contact.resubscribedOpt-out changes — only on a real state change. segment_id is null for an opt-out from all email.
contact_import.completedA CSV import finishes writing
form.submittedA hosted form receives a submission
email.queued / email.scheduledA send is accepted
email.delivered / email.failedA worker finishes a send
broadcast.sentA fan-out completes
channel_message.sent / channel_message.failedA Discord, Telegram or ntfy push finishes
domain.created / domain.verified / domain.deletedA sending domain changes
domain.verification_lostThe scheduled re-check demoted a verified domain

The same catalog drives automations — an integration you build and an automation someone configures react to exactly the same events.

Delivery behaviour ​

Deliveries are queued, not made inline with the request that caused them, so a slow endpoint of yours never slows the API call that triggered it.

Failures are retried, each attempt waiting 30 seconds longer than the last; exhausted retries are dead-lettered rather than dropped.

Emitting is best-effort by design. If the broker is unavailable the failure is logged and the originating request still succeeds — Notifyzr will not fail a customer's send because your endpoint was unreachable.

Return quickly. Acknowledge with a 2xx and do your work afterwards; a slow endpoint burns retry budget.

Released under the Apache 2.0 License.