Webhooks
Not available yet — do not build against this page
Outbound partner webhooks are not implemented. RekberPay does not currently POST to a partner URL, there is no place to register one, and no escrow.released or payment.completed delivery exists.
Everything below the "Planned contract" heading is a design document for a future release, retained so the contract is stable when it ships. It is not a description of current behaviour.
Today, use polling — see What to use instead directly below.
Earlier revisions of this page described the delivery, signature, and retry behaviour as if it were live. It was not, and that error is corrected here rather than quietly deleted: if you built a verification path against it, nothing was ever going to arrive at your endpoint.
What to use instead
Two endpoints cover the state changes webhooks would have pushed:
| Method | Path | Purpose |
|---|---|---|
GET | /api/escrows/:id/payment-status | Poll an escrow's payment state |
GET | /api/jasabayar/:id/payment-status | Poll a Jasa Pembayaran order's payment state |
GET /api/escrows/:id and GET /api/jasabayar/:id return the full current state including status and timestamps, which is what you would reconcile against a webhook anyway.
A reasonable poll cadence is every 5–10 seconds while a checkout is open, and backing off to once a minute afterwards. Because both endpoints are GET, they cost you nothing in idempotency bookkeeping.
If outbound webhooks would materially change your integration, say so at [email protected] — demand is what prioritises the work.
Planned contract
WARNING
Everything from here on is unimplemented. Do not write verification code against it yet.
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