Get started
Receive the result
The result of a payment arrives after the buyer leaves your site. Merxian sends it to your webhook endpoint as a signed event.
On this page
Why the redirect is not enough#
Merxian redirects the buyer to your successUrl after payment. Do not treat that visit as proof of payment:
- The buyer can close the browser before the redirect.
- The buyer can open your
successUrlwithout paying. - The payment result can arrive after the redirect.
Use webhook events to learn the result. Show the buyer a “thank you” page on successUrl, but fulfil only after a verified event or a read on your server.
Set up an endpoint#
-
Add a route on your server that accepts
POSTrequests with a JSON body.For example,
https://shop.example.com/webhooks/merxian. Live endpoints must usehttps. -
Register the endpoint in the dashboard, in the sandbox environment.
Give the URL and select the event types. For this guide, select
checkout.completed,payment.succeeded, andpayment.failed. -
Copy the signing secret.
The secret starts with
whsec_. The dashboard shows it once, when you create the endpoint. Store it on your server like an API key, for example asMERXIAN_WEBHOOK_SECRET.
See Configure an endpoint for all options.
Handle an event#
For each request that your endpoint receives:
- Verify the
Merxian-Signatureheader against the raw request body. Reject the request with400if the check fails. See Verify signatures. - Store the event
id. If you already processed thatid, return200and stop. Merxian can send an event more than once. - Return a
2xxstatus quickly. Do slow work after the response. - Act on the event
type.
| Event | Meaning | What to do |
|---|---|---|
checkout.completed |
The buyer paid on the hosted page. | Mark the checkout as done. Use client_reference_id to find your record. |
payment.succeeded |
The payment was captured. | Fulfil the sale. transaction_id names the transaction. |
payment.failed |
The payment failed. | Do not fulfil. The buyer can try again while the session is open. |
Webhook payloads use snake_case field names, such as transaction_id. The API uses camelCase.
{
"id": "whevt_7Kd2VpXn4TqB9mLzR",
"object": "event",
"type": "payment.succeeded",
"created_at": "2026-09-14T09:24:53Z",
"api_version": "2026-08-01",
"account_id": "acct_5Rn8bQ2xW7mK4tLzP",
"data": {
"object": {
"id": "pay_3RtN6wYc8mK2hQvJd",
"object": "payment",
"status": "succeeded",
"transaction_id": "txn_0F8mQ2rXbT4kL9pZa",
"checkout_id": "cs_6HvB2nQx9LmT4kWpR",
"amount": 14280,
"currency": "EUR"
}
}
}The example shows some fields only. The amount 14280 in EUR is €142.80. The full payload is on each event page.
Events can arrive in any order. For example, payment.succeeded can arrive before checkout.completed. Write each handler so that it works in either order.
Read the transaction as a fallback#
If an event is late, or you need the current state, read the transaction from your server:
curl https://api.merxian.com/v1/transactions/txn_0F8mQ2rXbT4kL9pZa \
-H "Authorization: Bearer $MERXIAN_API_KEY"status is completed and paymentStatus is paid when the transaction is fully paid. This read needs the transactions:read scope.