Webhooks
Process events
Merxian delivers each event at least once and in no fixed order. Build your handler so that a duplicate or a late event does no harm.
On this page
What Merxian guarantees#
| Guarantee | Meaning for your handler |
|---|---|
| At least once | An event can arrive more than once. A duplicate can arrive even after your endpoint returned 2xx. |
| Same event, same body | Every delivery of an event has the same id and the same body. |
| No order | Events can arrive in a different order from the changes. payment.succeeded can arrive before payment.processing. |
| Snapshot | The payload shows the resource when Merxian created the event. It can be older than the current state. |
Process each event once#
Store the event id when you process an event. Before you process an event, check whether you stored its id.
export async function handleEvent(event, db) {
// processed_events has a unique key on event_id.
const inserted = await db.insertIfAbsent('processed_events', { event_id: event.id })
if (!inserted) return // A duplicate. It was processed before.
switch (event.type) {
case 'payment.succeeded':
await markPaid(event.data.object.transaction_id)
break
case 'refund.succeeded':
await recordRefund(event.data.object.id, event.data.object.amount)
break
default:
// Ignore event types that you do not use.
break
}
}Put the check and the effect in one unit of work when you can. Then a crash between the two does not lose the event or process it twice.
Make each effect safe to repeat too. For example, “mark transaction txn_0F8mQ2rXbT4kL9pZa as paid” is safe to repeat. “Add one to the paid count” is not.
Do not depend on order#
Do not assume that events arrive in the order of the changes. Use one of these methods:
- Read the current state. When an event arrives, read the resource from the API, for example with Retrieve a payment. Act on the state that the API returns.
- Move only forward. Keep your own state for each record. Accept an event only if it moves the record forward. For example, after you mark a payment as succeeded, ignore a later
payment.processingfor that payment.
Final states are a safe signal. A transaction.completed event means that the transaction collected its full amount, in any order of arrival.
Answer before slow work#
Your endpoint must answer within 10 seconds. Use this order:
- Verify the signature. See Verify signatures.
- Store the event, for example in a queue or a table.
- Return
200. - Process the event from the queue.
If processing fails, retry from your queue. Do not return a 5xx status to get a new delivery for a failure in your own processing. Merxian retries only for a limited time. See Delivery and retries.
Handle unknown events and fields#
- Ignore an event
typethat you do not use. Return2xxfor it, so that Merxian does not retry it. - Ignore fields that you do not know. Merxian can add fields to a payload version.
- Read state values as strings. Webhook state values can differ from API values. See State values in payloads.