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

EventMeans
deliveredThe receiving server accepted it.
bouncedRejected. A hard bounce means the address is gone.
deferredTemporarily refused; Sentio will retry.
droppedNot attempted, usually because the address was suppressed.
opened, clickedEngagement, with bot detection so a mail scanner's open is not counted as a human one.
unsubscribedThe 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.