Webhook Responses
Webhook When Tiny Relay sends a webhook event to your specified endpoint, the response will include the relevant event data as outlined below. To ensure you successfully receive these events, you will need to whitelist the following IP address from which Tiny Relay will send the events: 3.229.12.181. Please configure your firewall or security settings accordingly to allow traffic from this IP.
List of Webhook Events
| Event | Description |
|---|---|
| SEND | Event triggered when email is sent successfully |
| DELIVERED | Event triggered when email gets delivered |
| BOUNCED | Event triggered if email gets bounced |
| OPEN | Event triggered when email is opened by the recipients |
| CLICK | Event triggered if recipients clicks on a redirect link present in the email |
| UNSUBSCRIBE | Event triggered if receiver clicks on the unsubscribe link |
| SPAM | Event triggered if email gets marked as spam |
Verifying the signature
Every webhook request we send is signed. Verify it before you trust the payload. Allowlisting our sending IP is not authentication — anything that can reach your endpoint can post to it.
Each request carries three headers:
| Header | Meaning |
|---|---|
svix-id | The delivery id. Stable across retries of the same event — deduplicate on it. |
svix-timestamp | When the delivery was signed, as a Unix timestamp in seconds. |
svix-signature | One or more space-separated signatures, each as v1,<base64>. |
The signature is an HMAC-SHA256 over {svix-id}.{svix-timestamp}.{body}, where body is the raw
request body exactly as received, keyed with your endpoint's signing secret. Compare with a constant-time
comparison, and reject a request whose svix-timestamp is outside a tolerance you choose (five minutes is
a reasonable default) so an old, valid request cannot be replayed.
svix-signature may list more than one signature — during a secret rotation both the old and new secret
sign the request. Treat the request as authentic if any listed signature verifies.
Webhooks are delivered through Svix, so the official Svix libraries verify all of this for you and are the recommended path. Verifying by hand is supported — the scheme above is all of it.
Your signing secret
Each endpoint has its own signing secret, so a secret is specific to one relay and is never shared across accounts. It is provided to you when your relay endpoint is configured.
Treat it as a credential: store it where you keep other secrets, never in client-side code or a public repository. To rotate it, contact support — rotation is not yet available through the API.
Delivery semantics
Delivery is at-least-once. If your endpoint times out or returns a 5xx we retry, and every retry of a given event repeats the same delivery id header — deduplicate your processing on that id rather than assuming each request is a distinct event.
On top of that, we suppress repeats before they leave us. Each event is given an identifier derived from the event's own identity — including the recipient, the event type and the time the event happened — so re-processing the same event reproduces the same identifier.
That suppression lasts 24 hours. Within the window, a repeat of an event you have already been sent is dropped before delivery. Beyond it, the same event re-processed would reach you again, so size your own deduplication store accordingly: 24 hours is how long we do it for you.
What the identifier does and does not collapse:
- A genuine repeat still reaches you. A second open or click by the same recipient happens at a different time, so it is a different event and is delivered on its own.
- A recipient listed twice in one send receives one
SENDevent, not two, because the two are identical on every field we can distinguish them by. - An event missing part of its identity is not deduplicated at all and may reach you more than once. That means an event with no occurrence time, no recipient, or no event type. This is deliberate — a duplicate you can discard, a dropped event you cannot recover.
Replay window
Event payloads are retained for 5 days currently. A missed event can be redelivered within that window; after it passes, the payload is gone and cannot be replayed. Retention does not affect normal delivery or retries.
Request
- application/json
Body
Possible values: [SEND, DELIVERED, BOUNCED, OPEN, CLICK, UNSUBSCRIBE, SPAM]
Represents the type of event that has occurred.
Possible values: [HARD_BOUNCED, SOFT_BOUNCED]
Represents the sub-type of the main event. For example, in the case of a bounced event we can have hard or soft bounces.
A string representing Unique identification(UUID) for each webhook request
Receiver's email address
Request Id provided sent as response to Send Email
Campaign Id provided to Send Email to track the campaign. If not provided, the generated requestId is used as the campaignId.
Metadata is stored against this campaignId. All requests using the same campaignId share the same metadata slot, and the latest metadata value replaces the previous value.
metadata object
Metadata associated with the send, for the recipient in receiver: the request-level metadata merged with that recipient's own metadata, with the recipient's keys winning. A send with no per-recipient metadata carries the request-level map alone.
This field is always present on v1 webhook payloads.
The metadata is echoed using normalized lowercase snake_case keys. If the send had no metadata, the metadata has expired, or the metadata could not be resolved, this field is returned as an empty object.
Metadata is best-effort and its absence does not delay or prevent webhook delivery.
Time the event occurred, in milliseconds since the Unix epoch (UTC), as a string.
Always present. If the event reached us without a time of its own, this falls back to the time we
processed it — so the field is never empty, but it is not always the moment the event happened. Use
eventTime when you need to tell those two cases apart.
Changed: this field previously carried a nanosecond counter (a 16-digit value such as
9748919598886666) that was not a wall-clock time and could not be converted to one. It is now a 13-digit epoch-millisecond timestamp. If you parserequestTime, check the value length.
Time the event actually occurred, in milliseconds since the Unix epoch (UTC).
This is the time the event happened, not the time the webhook was delivered. Delivery is asynchronous and retried, so the time your endpoint receives a request is not a reliable substitute. Use this field to order or timestamp events.
null on the rare event that reaches us without a time of its own. Unlike requestTime, this field
does not fall back to processing time — a null here is the signal that the occurrence time is
genuinely unknown.
Note: the interactive request builder on this page does not list this field yet. It is sent on every event type; the schema above is authoritative.
Represents the category of bounce.
Represents the description of bounce(dsn_diag).
Represents the status of bounce(dsn_status).
CLICK events only. The destination URL the recipient clicked, exactly as it appears in the email,
query string included. Use it to tell apart clicks on different links in the same email.
Not sent on any other event type: the key is left out of the payload, not set to null. It is also left
out of a CLICK event whose link could not be resolved; the event is still delivered.