Webhooks
Send purchases, renewals, signups and other events to your own endpoint: add a webhook, test it, verify the signature, read delivery health, redeliver, and pause or delete.
A webhook sends a message to an address of yours every time a chosen event happens in your workspace, for example a new purchase. When you finish, you will have an active webhook, a signing secret to verify its messages, and a place to watch deliveries. Webhooks are part of every current Zanfia plan.
Add a webhook
Open Webhooks
In the sidebar open Workflows → Webhooks (/workflows/webhooks) and click Add webhook. A
full-page form titled Add webhook opens (/workflows/webhooks/new).
Enter the address
Paste the address of your endpoint into Webhook URL. It has to be a full URL, for example
https://domain.com/hook; anything else is rejected before the form can be saved or tested.
Add headers or basic auth (optional)
Open Custom headers and basic auth. Add header adds a key and a value that are sent with every delivery. Basic auth credentials takes a Username and a Password. "Credentials are stored encrypted and never shown again": when you edit the webhook later, header values show as unchanged and you only type them to replace them.
Choose the events
Under Which events should we send? tick at least one event. The events are grouped into Sales & checkout, Subscriptions & access, Customers and Milestones & referrals; the full list is in the table below.
Send a test
In Test your webhook URL pick an event and click Send test. The dark box shows the exact
message that is sent. A green notice "Test delivery sent - endpoint responded 200" means your
endpoint answered; an answer outside the 200 range shows as a warning, and "Test delivery failed -
the endpoint could not be reached" means the address did not respond at all. Test messages carry
"test": true.
Save and copy the signing secret
Leave Active on and click Save webhook. A window titled Your webhook signing secret
shows a code starting with whsec_. Copy it and click OK. You are back on the list; the new
row shows a POST tag, the address, its events, and the Active switch turned on.
Good to know
The signing secret is not lost if you skip copying it. Open the webhook, click Edit, and use Reveal next to Signing secret. Roll secret generates a new one: "The current secret stops validating immediately. Deliveries keep flowing, but your server must be updated with the new secret to verify them."
Events you can subscribe to
| Group | Event in the form | event value in the message |
|---|---|---|
| Sales & checkout | New purchase | product.ProductPurchased |
| Free signup completed | product.FreeSignupCompleted | |
| Form submitted | form.FormSubmitted | |
| Order created (incl. failed payment) | product.OrderCreated | |
| Order refunded | product.OrderRefunded | |
| Checkout abandoned | checkout.CheckoutAbandoned | |
| Subscriptions & access | End of subscription | product.ProductSubscriptionEnd |
| Subscription renewal | product.ProductSubscriptionRenewed | |
| Renewal of the access period | product.ProductAccessRenewed | |
| End of access period | product.ProductAccessPeriodEnd | |
| 14 days, 7 days, 3 days, 1 day until the end of the access period | product.ProductAccessPeriodEndingIn14Days, …In7Days, …In3Days, …In1Day | |
| Customers | Subscribed to the newsletter | client.SubscribedToAudience |
| Tag added to a customer | client.TagAdded | |
| Subscriber went quiet | client.Unengaged | |
| The client has been imported | product.ClientImported | |
| Client archived in the product | product.ProductClientArchived | |
| Client removed from the product | product.ProductClientRemoved | |
| Milestones & referrals | Course completed | course.CourseCompleted |
| Validated referral | productReferrals.ValidSubscriberFromReferral | |
| Referral reward earned | productReferrals.ReferralRewardEarned |
The 14-day notice is sent only for access lasting at least 3 months.
What a delivery looks like
Every delivery is an HTTP POST with a JSON body and these headers, plus any custom headers and basic auth you configured:
| Header | Value |
|---|---|
X-Zanfia-Event | The event name, so you can route without parsing the body |
X-Zanfia-Signature | t=[unix time],v1=[signature], see below |
X-Zanfia-Test | true, present only on test messages sent from the dashboard |
The body always has the same envelope. The keys listed under data are always present and are null when the event carries no value for them; an event can add its own extra keys (for a purchase: productName, paymentType, quantity, price).
{
"apiVersion": "2026-07-13",
"event": "product.ProductPurchased",
"occurredAt": "2026-09-30T10:00:00.000Z",
"workspaceId": "your workspace id",
"data": {
"clientId": "cli_4f81c92a7b3d",
"clientEmail": "anna.novak@example.com",
"clientFirstName": "Anna",
"clientLastName": "Novak",
"clientName": "Anna Novak",
"productId": "prod_8c31a2f4d95e",
"priceId": "price_5e92c7d1a48b",
"orderId": "35e479ee-d456-4ee9-a5da-e7ff8109ffcc",
"checkoutId": "chk_1f83b6a92c4d",
"orderTotalAmount": 199,
"consents": [{ "name": "marketing", "value": true }],
"utmSource": "facebook",
"utmMedium": "cpc",
"utmCampaign": "summer-launch",
"utmTerm": null,
"utmContent": null
}
}
The preview box in the editor shows the body for whichever event you select, so use it as the reference for each event's extra keys.
Verify the signature
Each delivery carries X-Zanfia-Signature: t=[unix time],v1=[signature]. The signature is the HMAC-SHA256, in hex, of the text [t].[raw body] (the t value, a dot, then the request body exactly as received), computed with your signing secret. On your server:
- Read
tandv1from the header. - Compute HMAC-SHA256 over
t, a dot and the raw request body, with thewhsec_secret as the key. - Compare your result with
v1using a constant-time comparison, and reject the request if they differ.
Compute over the raw body, before any JSON parsing; a re-serialized body produces a different signature.
Monitor, redeliver, pause and delete
The list at /workflows/webhooks has four columns: Endpoint, Events, Health and Active.
| What you see | Meaning |
|---|---|
| Healthy, with the time of the last delivery | Recent deliveries succeeded. |
| "[n]/[m] recent deliveries failed" | Some recent deliveries failed. |
| No deliveries yet | Nothing was sent so far. Test messages from the editor are not counted. |
| Auto-paused next to the switch | "Paused automatically after 15 failed deliveries in a row. Fix the endpoint, then switch it back on." |
Click a row to open Webhook details: the address, its state, its events, and Recent deliveries with time, status and response. The redeliver arrow on a delivery sends that event again through the webhook's current settings ("Delivery re-sent - endpoint responded 200"). The buttons at the bottom are Delete ("Delete this webhook?"), Send test and Edit.
To pause a webhook without deleting it, turn its Active switch off in the list; it stops receiving events until you turn it back on.
Webhooks inside workflows
If a workflow of yours contains a webhook action, its address appears in a second table, Used in workflows, with the workflows that call it and the same health and delivery history. Those endpoints are edited in the workflow itself, not here. See Build a workflow for workflows.
Was this article helpful?

