Skip to main content

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​

EventDescription
SENDEvent triggered when email is sent successfully
DELIVEREDEvent triggered when email gets delivered
BOUNCEDEvent triggered if email gets bounced
OPENEvent triggered when email is opened by the recipients
CLICKEvent triggered if recipients clicks on a redirect link present in the email
UNSUBSCRIBEEvent triggered if receiver clicks on the unsubscribe link
SPAMEvent 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:

HeaderMeaning
svix-idThe delivery id. Stable across retries of the same event — deduplicate on it.
svix-timestampWhen the delivery was signed, as a Unix timestamp in seconds.
svix-signatureOne 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 SEND event, 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​

Body

    eventType string

    Possible values: [SEND, DELIVERED, BOUNCED, OPEN, CLICK, UNSUBSCRIBE, SPAM]

    Represents the type of event that has occurred.

    subType string

    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.

    notificationId string

    A string representing Unique identification(UUID) for each webhook request

    receiver string

    Receiver's email address

    requestId string

    Request Id provided sent as response to Send Email

    campaignId string

    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.

    property name* string
    requestTime string

    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 parse requestTime, check the value length.

    eventTime int64

    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.

    bounceCategory string

    Represents the category of bounce.

    bounceDescription string

    Represents the description of bounce(dsn_diag).

    bounceStatus string

    Represents the status of bounce(dsn_status).

    url string

    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.

Loading...