Webhooks
Receive events
Merxian sends each event as an HTTP POST request with a JSON body. Your endpoint must verify it and answer with a 2xx status within 10 seconds.
The request#
| Part | Value |
|---|---|
| Method | POST |
| URL | The URL of your endpoint |
| Body | The event envelope, as JSON in UTF-8 |
Headers#
| Header | Value |
|---|---|
Content-Type |
application/json |
User-Agent |
Merxian-Webhooks/1.0 |
Merxian-Signature |
The timestamp and one or more signatures. See Verify signatures. |
Merxian-Event-Id |
The event ID, whevt_…. It is the same as id in the body. |
Merxian-Delivery-Id |
The ID of the delivery of this event to this endpoint, whd_…. Retries of the delivery keep the same ID. |
Merxian-Webhook-Version |
The payload version of the endpoint, for example 2026-08-01. |
Keep the Merxian-Delivery-Id in your logs. It identifies one delivery if you need help from Merxian.
The event envelope#
{
"id": "whevt_7Kd2VpXn4TqB9mLzR",
"object": "event",
"type": "refund.succeeded",
"created_at": "2026-09-20T13:02:59Z",
"api_version": "2026-08-01",
"account_id": "acct_5Rn8bQ2xW7mK4tLzP",
"data": {
"object": {
"id": "rfnd_9KcT4mW2xQ7bN5vLp",
"object": "refund",
"status": "succeeded",
"transaction_id": "txn_0F8mQ2rXbT4kL9pZa",
"payment_id": "pay_3RtN6wYc8mK2hQvJd",
"amount": 5000,
"currency": "EUR"
}
}
}The example shows only some fields of the refund. 5000 in EUR is €50.00.
| Field | Type | Description |
|---|---|---|
id |
string | The event ID, whevt_…. It is the same on every delivery of the event. Use it to find duplicates. |
object |
string | Always event. |
type |
string | The event type, for example payment.succeeded. |
created_at |
string (date-time) | When Merxian created the event, in ISO 8601 UTC. It is not always the time of the change. |
api_version |
string | The payload version of the endpoint. |
account_id |
string | The account of the event, acct_…. |
data.object |
object | The resource of the event. Each event page shows its fields. |
data.previous_attributes |
object | On some update events, such as customer.updated, the fields that changed, with their values before the change. Absent on other events. |
Payloads use snake_case field names. Some state values differ from the API values. See State values in payloads.
Ignore fields that this documentation does not list. Merxian can add fields to a payload version.
The response#
| Your response | What Merxian does |
|---|---|
Any 2xx status within 10 seconds |
The delivery succeeded. Merxian ignores the response body. |
408, 429, or 5xx |
Merxian tries again later. |
| No response within 10 seconds, or a connection error | Merxian tries again later. |
A 3xx redirect |
The delivery fails. Merxian does not follow redirects and does not try again. |
Any other 4xx |
The delivery fails. Merxian does not try again. |
See Delivery and retries for the retry schedule.
A minimal handler#
This Node.js example uses only the standard library. It is an example, not an SDK. verifySignature is the function on Verify signatures.
import { createServer } from 'node:http'
import { verifySignature } from './verify.js'
const secret = process.env.MERXIAN_WEBHOOK_SECRET
createServer((request, response) => {
if (request.method !== 'POST' || request.url !== '/webhooks/merxian') {
response.writeHead(404).end()
return
}
const chunks = []
request.on('data', (chunk) => chunks.push(chunk))
request.on('end', () => {
const rawBody = Buffer.concat(chunks)
if (!verifySignature(rawBody, request.headers['merxian-signature'], secret)) {
response.writeHead(400).end()
return
}
const event = JSON.parse(rawBody.toString('utf8'))
// enqueue is your own function. Store the event, then process it after you answer.
enqueue(event)
response.writeHead(200).end()
})
}).listen(3000)Return 400 for a request with a signature that is not valid. Merxian does not retry a 4xx, so a real event gets no retry after this answer. Make sure that your secret is correct before you go live.