Skip to main content
Every Referly webhook delivery is the same shape: a POST carrying a JSON object with two keys. This page documents that envelope, then the fields inside each of the five object families. For what causes each event to fire, see Event types.

The HTTP request

The body is always UTF-8 JSON. Verify the signature against the raw bytes before parsing — see Verify signatures.

The envelope

event
string
required
The event type that fired, always family.action — for example sale.created. Identical to the Svix event type on the message.
body
object
required
The object the event happened to, with its useful relations expanded. For deleted events this contains only id.
There is no data, no previous_attributes, and no envelope-level timestamp — use svix-timestamp or the object’s own createdAt.

Field conventions

These hold across every family.

Enum values are uppercase

Enums serialise exactly as stored: "PURCHASE", "ACTIVE", "STRIPE", "LINK". Compare case-sensitively against the uppercase form.
The example payloads in the dashboard’s Event Catalog show lowercase values such as "purchase" and "active". Those samples are illustrative — live deliveries send the uppercase enum. Match on uppercase.

Dates are ISO 8601 strings

createdAt, updatedAt, expiresAt, refundedAt, and friends are ISO 8601 with a timezone, for example "2026-03-11T09:24:18.442Z". Never a Unix timestamp.

IDs are UUID strings, except sales

Affiliates, referrals, coupons, and promotional codes use UUID strings. Sales use an auto-incrementing integer, so body.id on a sale event is a number, not a string, and so is the id on sale.deleted. Type your handler accordingly.

Money is a plain number in the program’s currency

Amounts such as totalEarned, commissionEarned, and totalRevenue are decimal numbers in your program’s currency — 199.99, not 19999 minor units. There is no per-payload currency field on sales or referrals; the currency is a property of the program.

Assume nullable

Most fields other than id are nullable. email, name, affiliateId, referralId, commissionRate, and every relation can be null. Code defensively rather than trusting a field to be present because it was present last time.

Sale payloads

The largest and most commonly consumed payload. Emitted on sale.created and sale.updated.

commissionEarned versus commissions

A single sale can generate several commissions — the selling affiliate’s, plus a parent’s under multi-level rules, plus bonuses. The commissions array holds all of them. The top-level commissionEarned is a convenience: it is the commissionEarned of the commission belonging to this sale’s own affiliate. If you only pay the direct affiliate, read the top-level field. If you mirror the full ledger, iterate commissions. Each entry in commissions carries id, affiliateId, commissionEarned, approvalStatus (ON_HOLD, ACCEPTED, or DECLINED), rewardType (CASH or NON_CASH), payoutCreated, payoutId, manuallyPaid, holdReason, bonusReason, createdAt, and updatedAt.
Payout state lives on the commission, not the sale. payoutCreated, payoutId, and manuallyPaid are fields inside commissions[] — there are no top-level equivalents on the sale object, and no expanded payout object, even though the Event Catalog schema lists them. To learn whether an affiliate has been paid for a sale, read commissions[].payoutCreated and commissions[].payoutId.

Example: sale.created

Referral payloads

Emitted on referral.created and referral.updated.

status is an alias

The stored column is subscriptionStatus. Referly copies it to status in the webhook payload for convenience, so both keys appear with the same value. Read either — but be aware status is a webhook-only convenience and is not a field on the referral elsewhere in the API.

Example: referral.created

Affiliate payloads

Emitted on affiliate.created and affiliate.updated. The affiliate is a user record, so this is the widest payload — the fields below are the ones you can rely on. In webhook payloads, link is populated with the slug of the affiliate’s first referral link as a convenience. That differs from the REST API, where the affiliate object’s link property is deprecated and usually null. An affiliate can own many slugs, so link is lossy. Read affiliateLinks whenever an affiliate might have more than one — see affiliates and referral links.

Example: affiliate.created

Coupon payloads

Emitted on coupon.created and coupon.updated. The body is the coupon with its promotionalCodes array expanded.

Promo code payloads

Emitted on promocode.created and promocode.updated. The body is the promotional code with its parent coupon expanded.

Delete events

All five deleted events share one shape. The object is gone, so there is nothing to expand:
This is the practical reason to store the id of everything you process. On a sale.deleted you get the integer and nothing else — no affiliate, no amount, no customer — so your own records are the only way to know what to reverse.

Payloads are snapshots

The body is serialised once, when the event is sent. It is not re-read on delivery. That has two consequences:
  • A retry delivers the original payload. If a delivery fails at 09:00 and succeeds on retry at 14:00, you receive the 09:00 state, not the current state. See Retries and logs.
  • Events can arrive stale or out of order. A sale.updated that succeeded on first try can land before a sale.created that needed three attempts. Where ordering matters, compare createdAt or re-read the object through the REST API before acting on it.

Fields not listed here

Payloads are built by spreading the stored record, so a delivery may contain fields beyond this reference — internal columns, newly added fields, relations included for other reasons. Treat anything not documented here as unstable: safe to log, not safe to depend on. Equally, do not assume a documented field is always present — parse defensively and ignore what you do not recognise, so a new field never breaks your handler.

Event types

Which events produce which of these bodies.

Verify signatures

Check the signature before you parse the body.

Retries and logs

Inspect the exact payload that was delivered.

Data model

How these objects relate to each other.

Statuses and enums

Every enum value you can receive.

Test and replay

Send yourself a real payload to inspect.
Last modified on July 21, 2026