Skip to main content
Webhook management is server-side. Register and delete endpoints with the Org-API-Key from your backend, and verify deliveries on a backend HTTPS endpoint. Never manage webhooks from an app.
Webhooks push lifecycle events from Expys to an HTTPS endpoint you control, so your systems react to redemptions, point changes, member changes, and concierge messages without polling.

Register an endpoint

Create an endpoint with the events you want. The response includes the signing secret once - store it immediately; it is never returned again.
string
required
Endpoint id (use it to delete the endpoint).
string
required
Your HTTPS delivery URL.
string[]
required
The subscribed event names.
string
required
SANDBOX or LIVE, from the key you used.
string
required
The HMAC signing secret, prefixed whsec_. Shown once - store it now.
string
required
ISO-8601 creation time.
Manage endpoints with GET /v1/webhooks (list) and DELETE /v1/webhooks/{id}. An org may hold up to 10 active endpoints per environment. See the API reference.

Event catalog

The redemption.* names cover the full booking lifecycle - one event per status transition - so a CRM sees the whole course of an experience.
Subscribing to an event name not in this catalog is rejected with WEBHOOK_EVENT_UNKNOWN. A non-HTTPS or disallowed URL is rejected with WEBHOOK_URL_NOT_ALLOWED.

The conversation.message_created payload

Fires for a message from either side, which is what makes it useful: your backend learns when our concierge has replied, not only when your own member has written.
The full message body is never sent over a webhook, only preview. Read the message itself through the API when you need it.
Internal notes never fire. Our team can leave notes on a conversation that the member does not see; those emit no event at all - including notes with files attached.
No download URL is ever sent over a webhook. Webhook payloads are retried, stored and logged by their nature, and a signed download link sitting in a log file is a publicly readable ticket for as long as it lives. hasAttachments tells you to go and read the message; the links come from there, minted fresh.

The redemption.canceled payload

The data object mirrors the REST redemption shape exactly, plus the externalUserID and pointsSpent a CRM needs to attribute the event:
canceledReason and canceledNote are present on every redemption.* event, null on all of them but this one, so you never have to check the status before reading them. canceledNote is written for the member and is safe to display. See Redemptions for the six reason values.

Delivery format

Each delivery is a POST with a JSON body and these headers:

Verify the signature

Recompute HMAC-SHA256 over the exact raw request body (not a re-serialized object) with your signing secret, hex-encode it, prefix sha256=, and compare in constant time against X-Expys-Signature.
Read the raw body bytes before any JSON parsing middleware reshapes them. Verifying against a re-serialized object will fail because key order and whitespace differ from what was signed.

Retries and dead-lettering

A delivery is retried on any non-2xx response or timeout, with exponential backoff: first retry after 30s, doubling each time, capped at 1 hour. After 6 total attempts the delivery is dead-lettered and no longer retried.
Each delivery request times out after 10 seconds, so respond quickly. Do the real work asynchronously - acknowledge with a 2xx as soon as you have verified the signature and enqueued the event.

Consume idempotently

Deliveries are at-least-once: a retry can arrive after you already processed an event. Deduplicate on X-Expys-Delivery (or the event’s own id) and make handlers idempotent so a duplicate is a no-op.