Webhooks
RekberPay POSTs JSON to your configured webhook URL when escrow state changes. This page documents every event, the signature scheme, and the retry policy.
For the integration walkthrough (verify, handle retries, idempotency), see Handling webhooks.
Delivery
- Method:
POST - Content-Type:
application/json - From IPs: Documented at
https://api.rekberpay.com/.well-known/webhook-ips(allowlist these in your firewall) - Timeout: 5 seconds — return 2xx within that window
- Retries: Up to 7 attempts on non-2xx (see retry table)
Signature
Every webhook includes a signature field — HMAC-SHA256 of the canonical event string, hex-encoded.
Canonical string:
event + escrow_id + amount + status + timestampVerify with:
import crypto from 'node:crypto';
function verify(body, secret) {
const canonical = `${body.event}${body.escrow_id}${body.amount}${body.status}${body.timestamp}`;
const expected = crypto.createHmac('sha256', secret).update(canonical).digest('hex');
return crypto.timingSafeEqual(
Buffer.from(body.signature, 'hex'),
Buffer.from(expected, 'hex'),
);
}See Authentication for the HMAC scheme. Same algorithm as link signing, different secret.
Common envelope
Every webhook has these fields:
| Field | Type | Description |
|---|---|---|
event | string | Event name (see below) |
escrow_id | UUID | The escrow this event is about |
ref | string | Your ref from when you signed the link |
amount | integer | IDR, no decimals |
status | string | The escrow's state after this event |
timestamp | ISO 8601 | When the event occurred (UTC) |
data | object | Event-specific extra fields |
signature | hex string | HMAC-SHA256 verification signature |
Events
escrow.created
Fired immediately after a partner-signed escrow is created.
| Field | Type | Description |
|---|---|---|
data.expires_at | ISO 8601 | When the escrow auto-cancels if unpaid |
data.escrow_url | URL | Buyer payment link |
{
"event": "escrow.created",
"escrow_id": "7f8a9b0c-1d2e-3f4a-5b6c-7d8e9f0a1b2c",
"ref": "MARKETPLACE_98765",
"amount": 22500000,
"status": "pending_payment",
"timestamp": "2026-05-18T07:00:00.000Z",
"data": {
"expires_at": "2026-05-25T07:00:00.000Z",
"escrow_url": "https://rekberpay.com/escrow/7f8a.../pay"
},
"signature": "..."
}payment.completed
Buyer paid, funds in escrow.
| Field | Type | Description |
|---|---|---|
data.payment_method | string | qris, bank_transfer, gopay, shopeepay, crypto |
data.paid_at | ISO 8601 | When the gateway confirmed |
data.fee | integer | Platform fee withheld on release |
{
"event": "payment.completed",
"escrow_id": "7f8a...",
"ref": "MARKETPLACE_98765",
"amount": 22500000,
"status": "paid",
"timestamp": "2026-05-18T09:27:00.000Z",
"data": {
"payment_method": "qris",
"paid_at": "2026-05-18T09:26:55.000Z",
"fee": 5000
},
"signature": "..."
}escrow.sent
Seller marked the goods/service delivered.
| Field | Type | Description |
|---|---|---|
data.tracking_number | string | null | If the seller provided one |
data.courier | string | null | Courier name (e.g., JNE, J&T) |
data.delivered_at | ISO 8601 | Server-side timestamp |
escrow.received
Buyer confirmed receipt.
{
"event": "escrow.received",
"escrow_id": "7f8a...",
"ref": "MARKETPLACE_98765",
"amount": 22500000,
"status": "received",
"timestamp": "2026-05-19T14:00:00.000Z",
"data": {
"confirmed_at": "2026-05-19T14:00:00.000Z"
},
"signature": "..."
}escrow.released
Funds released to seller balance. Terminal state.
| Field | Type | Description |
|---|---|---|
data.released_by | string | buyer, auto_release, or admin |
data.release_method | string | manual or sla_timeout |
data.seller_payout | integer | Amount credited to seller's RekberPay balance |
escrow.refunded
Funds refunded to buyer. Terminal state.
| Field | Type | Description |
|---|---|---|
data.refunded_by | string | admin or auto_refund |
data.refund_reason | string | Free-form note |
escrow.disputed
Buyer or seller opened a dispute.
| Field | Type | Description |
|---|---|---|
data.opened_by | string | buyer or seller |
data.reason | string | not_as_described, not_received, damaged, fraud, other |
data.description | string | Free-form text from the disputer |
dispute.resolved
Admin resolved a dispute.
| Field | Type | Description |
|---|---|---|
data.outcome | string | released, refunded, or hold |
data.resolution_note | string | Admin's note |
status reflects the new escrow state (released, refunded, or hold).
What status looks like
The status field is the escrow's state after the event:
| Event | Resulting status |
|---|---|
escrow.created | pending_payment |
payment.completed | paid |
escrow.sent | sent |
escrow.received | received |
escrow.released | released |
escrow.refunded | refunded |
escrow.disputed | disputed |
dispute.resolved | released, refunded, or hold |
Subscribing to events
By default, you receive all events. To filter, email [email protected] with the events you want.
We don't currently support per-escrow event subscriptions — partner-level only.
Replaying
Failed deliveries can be replayed from the partner dashboard at https://dashboard.rekberpay.com/webhooks/<event-id>/replay. Replays carry the same event_id so your idempotency key works.
See also
- Handling webhooks — integration guide
- Authentication — HMAC verification
- Creating escrows — what triggers each event