When you set up a webhook, you choose which events it receives.
This article explains each one: when it's sent, what's inside, and what it's typically used for.
The full technical reference, with every field, is in the app: Settings → Integrations → Documentation → Webhooks.
The four events
Event | Sent when… |
| A traveler paid through a payment link |
| A payment attempt was declined |
| A refund has gone through |
| An alternative of a booking is fully paid |
Payments you record by hand in Ezus Pay (bank transfers, cash, cheques) don't trigger any event: they didn't go through Ezus Pay, and you already know about them.
What every message looks like
Every message has the same outer structure, whatever the event:
{ "id": "6f1c0b3a-7f2e-4a1d-9c55-4b2f1e0a8d33", "type": "payment.succeeded", "created": 1758700800123, "api_version": "2026-05-01", "agency_id": "0b8a4c2e-…", "data": { "object": { … } } }id: the message's unique identifier. It stays the same if the message is sent again, so your tool can recognize a duplicate.type: which event it is.created: when the event occurred (a timestamp in milliseconds).data.object: the event's content, described below.
The event type is also sent in the Ezuspay-Event-Type header, so your tool can route a message without reading it.
payment.succeeded: a payment was received
Sent when a traveler completes a payment on a payment link, by card or SEPA debit.
What's inside:
Field | What it is |
| The payment's ID in Ezus Pay. Use it to get the full details through the API, or to refund it |
| The booking: its ID, its reference, and its Ezus project ID if it comes from Ezus |
| The alternative the payment was made on |
| The payment link and its label, e.g. Deposit 30% |
| The amount paid, in cents |
|
|
| When the payment was made |
| The traveler's answers to your checkout fields, or |
Example:
{ "transaction_id": "3b9d…", "booking": { "id": "…", "ezus_project_id": "…", "reference": "EZ-2026-001" }, "alternative": { "id": "…", "ezus_alternative_id": "…" }, "payment_link": { "id": "…", "label": "Deposit 30%" }, "amount_cents": 150000, "currency": "EUR", "payment_method": "card", "paid_at": "2026-09-24T10:12:00.000Z", "custom_fields": [ { "key": "phone", "label": "Phone number", "type": "PHONE", "value": "+33 6 12 34 56 78" } ] }Typical uses: record the payment in your accounting tool, notify the salesperson, update the trip status in your CRM.
Good to know: the webhook doesn't include the payer's name. To get it, along with the refundable balance and the refund history, call GET /transactions/:id with the transaction_id.
payment.failed: a payment attempt was declined
Sent when a payment attempt fails: card declined, insufficient funds, expired card, failed authentication…
What's inside: the same transaction_id, booking, alternative, payment_link, amount and currency as above, plus:
Field | What it is |
| Why it failed, e.g. |
| A readable explanation, in English, as provided by the card network |
| When the attempt failed |
Typical uses: remind the traveler to try again, flag the booking to your team.
Good to know:
A traveler who retries generates one event per failed attempt, all with the same
transaction_id.A failure can be followed by a success. If a
payment.succeededarrives later with the sametransaction_id, the traveler managed to pay: don't send them a reminder.No money has moved. It's a signal to act on, not an accounting entry.
payment.refunded: a refund went through
Sent when a refund has actually been processed, whether it was made from Ezus Pay or through the API. A SEPA refund can take a few days before this event is sent.
What's inside: transaction_id, booking, alternative, payment_link and currency, plus:
Field | What it is |
| The refund's ID. A payment refunded in several steps generates one event per refund |
| The amount of this refund |
| The total refunded so far on this payment, all refunds included |
|
|
|
|
| When the refund went through |
Typical uses: record the refund in your accounting tool, update the balance due in your CRM.
Good to know: rely on total_refunded_cents to know where a payment stands. It stays correct even if your tool missed an earlier refund event.
booking.fully_paid: an alternative is fully paid
Sent when the payments collected on an alternative reach its total amount.
What's inside:
Field | What it is |
| The booking: its ID, its reference, its Ezus project ID |
| The alternative that is now fully paid |
| The alternative's total amount |
Typical uses: confirm the trip, trigger the final documents, celebrate in your team's Slack channel.
Good to know: it arrives together with the payment.succeeded of the payment that completed the balance. If you already process every payment.succeeded, you may not need this one.
The test event: ping
When you click Send a test on a webhook's page, your tool receives a ping event. It contains no business data and is never sent otherwise. Your tool should simply answer 200 to it. See Set up a webhook.
Rules that apply to every event
Amounts are in cents, as integers:
150000means €1,500.00.Some fields can be
null. For example,ezus_project_idandreferenceare empty on a booking created outside Ezus.The same message can arrive twice. Store each message's
idand ignore one you've already processed.Messages can arrive out of order. A message sent again after a failure arrives after newer ones. Rely on dates and totals rather than on arrival order.
The list of events and fields will grow. Your tool should ignore fields it doesn't know, and answer
200to an event type it doesn't handle.
See what was actually sent
Every message sent to your webhook is kept for 30 days. On the webhook's page, click a line in the Delivery log to see the exact content sent and how your tool answered.
