Webhooks
Delivery events at your system's URL, signed with HMAC-SHA256.
Register
{
"url": "https://your-app.com/webhooks/allyonemail",
"events": ["delivered", "bounced", "complained"],
"description": "Production"
}
Requires the admin scope (or the dashboard). The URL must be public — internal addresses are refused.
The full secret appears only in this response; later reads show it masked. To see a new value, use POST /api/v1/webhooks/:id/rotate-secret.
Events
| Event | When |
|---|---|
delivered | The recipient's server accepted the message. |
deferred | Temporary failure; AllyOneMail will try again. Not a bounce. |
bounced | The destination refused the message. |
complained | The recipient marked it as spam (provider feedback loop). |
opened | Open recorded by the pixel. |
clicked | Click on a tracked link (url in the payload). |
unsubscribed | Unsubscribe through the link or the one-click header. |
blocked | Send stopped before leaving (suppressed or blocked recipient). |
domain_verified | The domain passed DNS verification. |
ip_paused | A sending IP was paused due to reputation. |
reputation_dropped | Sharp reputation drop on an IP (carries bounce and complaint rates). |
billing_threshold_reached | Monthly usage crossed a plan alert threshold. |
incident_escalated | A deliverability incident was escalated (operational alert). |
The same event may arrive more than once — through a retry, or because delivered is emitted when the sending server accepts the message and again on the final delivery confirmation. Deduplicate by X-AllyOneMAIL-Id, or by message_id + event.
Payload
{
"event": "bounced",
"message_id": "msg_a1b2c3",
"to": "cliente@destino.com",
"provider": "gmail",
"response": "550 5.1.1 The email account that you tried to reach does not exist",
"timestamp": "2026-10-01T13:00:00.000Z",
"client_id": "c1f0..."
}
Every payload carries client_id. Send events carry message_id, to, provider and, depending on the source, from, response (the recipient server's reply) and latency_ms. opened and unsubscribed carry message_id, to and timestamp; clicked also carries url.
Verify the signature
X-AllyOneMAIL-Event: bounced
X-AllyOneMAIL-Sig: sha256=<hex>
Headers X-AllyOneMAIL-Event (the event) and X-AllyOneMAIL-Sig: sha256= + HMAC-SHA256 of the raw body with the secret. Compute it over the bytes received, before parsing. The X-AllyOneMAIL-Id header identifies the delivery and repeats across retries.
const expected = 'sha256=' + crypto.createHmac('sha256', secret).update(rawBody).digest('hex')
// compare in constant time against the header X-AllyOneMAIL-Sig
Delivery and failures
- Success is any
2xxwithin 10 seconds; redirects are not followed. - On failure, up to 3 attempts: right away, after ~1 minute and after ~2 minutes.
- Every attempt is in
GET /api/v1/webhooks/:id/deliveries(kept for 90 days). Resend a delivery withPOST /api/v1/webhooks/:id/deliveries/:deliveryId/retry. - After 10 consecutive deliveries failing all 3 attempts, the webhook is paused. Fix the endpoint and reactivate it with
PATCH /api/v1/webhooks/:id. - Respond
2xxquickly and process in a queue on your side.
Test
Fires a synthetic delivered to your URL, signed with the same secret.
