Event Webhooks
Event webhooks tell an agent what happened to its own mail. They are a different stream from inbound routes, with a different credential: inbound routes are administered per tenant with an operator key, while /v1/webhooks is scoped to the calling tenant.
Subscribe
curl -X POST "$API/v1/webhooks" \
-H "Authorization: Bearer $TENANT_KEY" -H 'Content-Type: application/json' \
-d '{
"url": "https://your-platform.example.com/hooks/events",
"event_types": ["delivered","bounced","dropped","deferred","opened","clicked","unsubscribed"]
}'Dispatch is retried, with per-endpoint concurrency caps, delivery logs, and a test-dispatch endpoint so you can exercise your handler without waiting for real mail.
Verify The Signature
Every dispatch carries four headers.
X-Sentio-Event: delivered
X-Sentio-Timestamp: 1787513589
X-Sentio-Nonce: <random>
X-Sentio-Signature: <hex hmac-sha256>The signature is HMAC-SHA256, hex-encoded, keyed by the webhook's signing secret, over this exact string:
"{timestamp}.{nonce}." + <raw request body>Note the trailing dot after the nonce: the prefix is timestamp, dot, nonce, dot, and then the body is concatenated directly.
Three things to get right, and each one is load-bearing.
- Verify against the raw bytes, before any JSON parsing. Deserialising and re-serialising changes the bytes and the signature will not match.
- Use a constant-time comparison. A byte-by-byte early return leaks the signature one character at a time.
- Reject timestamps outside a tolerance window. The timestamp and nonce are what stop a captured request being replayed; without that check a valid signature is valid forever.
Skip this and your events endpoint is an unauthenticated write path into your platform.
Events
| Event | Means |
|---|---|
delivered | The receiving server accepted it. |
bounced | Rejected. A hard bounce means the address is gone. |
deferred | Temporarily refused; Sentio will retry. |
dropped | Not attempted, usually because the address was suppressed. |
opened, clicked | Engagement, with bot detection so a mail scanner's open is not counted as a human one. |
unsubscribed | The recipient opted out, including via one-click unsubscribe. |
Four more lifecycle events exist and are worth knowing about even if you do not subscribe to them: queued and processed mark a message's progress before any delivery attempt, and held and released cover mail that was withheld and then let go. Engagement events (opened, clicked, unsubscribed) are a separate set from the delivery lifecycle in the code, which is why a subscription can mix them freely.
Bounces Deserve Product Behaviour
bounced and dropped are worth more than a log line. A hard bounce means that address is gone, and Sentio has already added it to the tenant's suppression list. An agent that keeps composing replies to a dead address wastes tokens and spends reputation for nothing.
The useful shape is: on bounced, mark the thread undeliverable and stop the agent working on it. On dropped, check the suppression list before assuming the send failed for a new reason.