Escrows API
The escrow endpoints are the core of any partner integration. You'll create escrows via signed links, redirect buyers to the hosted checkout, and poll or webhook your way to confirmation.
Endpoints
| Method | Path | Auth | Idempotent |
|---|---|---|---|
POST | /api/escrows/from-link | HMAC signature | Yes |
GET | /api/escrows/:id/preview | none (public) | — |
GET | /api/escrows/:id/payment-status | none (public) | — |
Create an escrow from a signed partner link.
Authentication: Session cookie (for the buyer caller) + HMAC signature in the sig field inside the URL. Idempotency: supported via Idempotency-Key header
Request
POST /api/escrows/from-link
Content-Type: application/json
Cookie: rekberpay_session=...
Idempotency-Key: 550e8400-e29b-41d4-a716-446655440000Body
| Field | Type | Required | Description |
|---|---|---|---|
link | URL | Yes | The fully constructed and signed partner URL (e.g. https://rekberpay.com/...?seller=...&sig=...) |
amount | integer | Yes | Price in IDR (10,000 – 500,000,000) |
The backend extracts and verifies the seller, item, category, ref, v, sig, expires, and desc directly from the query parameters of the provided link. Ensure the signed link has not expired and the signature is valid.
Example
curl -X POST https://api.rekberpay.com/api/escrows/from-link \
-H "Content-Type: application/json" \
-H "Idempotency-Key: 550e8400-e29b-41d4-a716-446655440000" \
-H "Cookie: rekberpay_session=..." \
-d '{
"link": "https://rekberpay.com/?seller=0a1b2c3d-4e5f-6a7b-8c9d-0e1f2a3b4c5d&item=iPhone%2015%20Pro%20256GB&category=goods&amount=22500000&expires=1748448000&ref=MARKETPLACE_98765&v=1&sig=f3a9c1e8b7d6a5f4c3b2a1e0d9c8b7a6f5e4d3c2b1a0f9e8d7c6b5a4f3e2d1c0",
"amount": 22500000
}'Response
201 Created
{
"success": true,
"data": {
"id": "7f8a9b0c-1d2e-3f4a-5b6c-7d8e9f0a1b2c",
"buyer_id": "9a8b7c6d-5e4f-3a2b-1c0d-9e8f7a6b5c4d",
"seller_id": "0a1b2c3d-4e5f-6a7b-8c9d-0e1f2a3b4c5d",
"item_name": "iPhone 15 Pro 256GB",
"amount": "22500000",
"description": "",
"category": "goods",
"status": "pending_payment",
"partner_ref": "MARKETPLACE_98765",
"partner_sig_version": 1,
"created_at": "2026-05-18T07:00:00.000Z",
"updated_at": "2026-05-18T07:00:00.000Z"
}
}| Field | Type | Description |
|---|---|---|
id | UUID | Escrow ID — store this against your order |
buyer_id | UUID | The authenticated user who made the request |
seller_id | UUID | The seller extracted from the link |
status | string | Always pending_payment on creation |
amount | string | Echo of the amount you sent |
partner_ref | string | Echo of your ref from the link |
Errors
| Status | Code | Cause |
|---|---|---|
400 | INVALID_BODY | Required field missing or wrong type |
400 | INVALID_URL | Malformed URL provided in link |
422 | HOST_NOT_ALLOWED | Link host is not permitted |
422 | UNSUPPORTED_SIG_VERSION | Signature version is not supported |
422 | SIG_INCOMPLETE | Signed links require ref, expires, and v |
422 | INVALID_EXPIRES | expires must be a unix timestamp |
422 | LINK_EXPIRED | Partner link has expired |
422 | EXPIRES_TOO_FAR | expires cannot be more than 30 days in the future |
422 | SIG_MISMATCH | Signature verification failed |
422 | INVALID_SELLER | seller must be a UUID v4 |
422 | SELLER_IS_BUYER | Cannot create escrow with yourself as seller |
422 | SELLER_NOT_FOUND | Seller user does not exist |
422 | INVALID_CATEGORY | category must be goods, service, or digital |
422 | INVALID_ITEM | item is required in the link |
Get public escrow details. No authentication required — useful for showing order details on your confirmation page before redirecting to payment.
Request
GET /api/escrows/7f8a9b0c-1d2e-3f4a-5b6c-7d8e9f0a1b2c/previewResponse
200 OK
{
"success": true,
"data": {
"id": "7f8a9b0c-1d2e-3f4a-5b6c-7d8e9f0a1b2c",
"item_name": "iPhone 15 Pro 256GB",
"description": "Brand new sealed",
"amount": 22500000,
"category": "goods",
"status": "pending_payment",
"seller": {
"id": "0a1b2c3d-4e5f-6a7b-8c9d-0e1f2a3b4c5d",
"display_name": "Toko ABC",
"kyc_verified": true
},
"expires_at": "2026-05-25T07:00:00.000Z",
"created_at": "2026-05-18T07:00:00.000Z"
}
}This endpoint exposes only public-safe fields. Buyer details, internal IDs, and audit history are never returned.
Errors
| Status | Code | Cause |
|---|---|---|
404 | NOT_FOUND | Escrow doesn't exist |
410 | EXPIRED | Escrow expired and was cancelled |
Poll payment status for a specific escrow. Authentication: session cookie (buyer, seller, or admin only).
Request
GET /api/escrows/7f8a9b0c-1d2e-3f4a-5b6c-7d8e9f0a1b2c/payment-status
Cookie: rekberpay_session=...Response
200 OK
{
"success": true,
"data": {
"id": "pay_8f9a0b1c2d3e4f5a6b7c8d9e",
"escrow_id": "7f8a9b0c-1d2e-3f4a-5b6c-7d8e9f0a1b2c",
"provider": "paymento",
"method": "crypto",
"gateway_order_id": "RBP-7f8a9b0c-1781234567890-a1b2",
"amount": "22612500",
"status": "pending",
"qr_url": null,
"va_number": null,
"redirect_url": "https://app.paymento.io/gateway?token=...",
"raw_response": "{}",
"expires_at": null,
"created_at": "2026-05-18T09:26:55.000Z",
"updated_at": "2026-05-18T09:26:55.000Z"
}
}| Field | Type | Description |
|---|---|---|
id | UUID | Internal payment link ID |
provider | string | Internal gateway provider name |
method | string | Payment method (e.g. qris, bank_transfer, crypto) |
gateway_order_id | string | The order ID sent to the gateway |
amount | string | Total amount to be paid (base escrow amount + gateway surcharge) |
status | string | One of pending, paid, expired, failed, awaiting_confirmation |
qr_url | URL | null | QRIS Code image URL (if applicable) |
va_number | string | null | Virtual Account number (if applicable) |
The raw_response object may contain internal provider metadata. It is highly advised not to rely on raw_response as it varies drastically across gateways.
Polling guidance
- Poll every 5 seconds while status is
pending - Stop polling once status is
paid,failed, orexpired - Cap total polling at 2 minutes then show a "payment pending" message
- Don't poll faster than 1 req/sec — you'll get rate-limited
Errors
| Status | Code | Cause |
|---|---|---|
401 | UNAUTHORIZED | User is not logged in |
403 | FORBIDDEN | User is not a party in this escrow |
404 | ESCROW_NOT_FOUND | Escrow doesn't exist |
404 | NO_PAYMENT | No payment attempt found for this escrow |
429 | RATE_LIMITED | Polling too fast |
Get shipping tracking for a physical-goods escrow: the persisted courier + AWB, a best-effort live journey snapshot, the official-courier deep link, and the courier list used by the seller's "mark shipped" form.
Authentication: session cookie (buyer, seller, or admin only).
The live snapshot is best-effort enrichment. The courier + AWB are always persisted and track_url always resolves, so a slow or unavailable upstream returns snapshot.found: false rather than failing the request. The snapshot is cached for ~10 minutes server-side.
Response
200 OK
{
"success": true,
"data": {
"tracking": {
"courier": "anteraja",
"courier_name": "AnterAja",
"number": "TSA-80531250806",
"track_url": "https://anteraja.id/tracking?awb=TSA-80531250806"
},
"snapshot": {
"found": true,
"courier": "anteraja",
"awb": "TSA-80531250806",
"status": "Delivered",
"recipient": "Diterima oleh Rizaldy (Penerima Asli)",
"events": [
{ "date": "25 Jun 2026 15:19", "description": "Delivery sukses, paket telah diterima." },
{ "date": "25 Jun 2026 10:31", "description": "Paket sudah di-pickup oleh kurir." }
],
"checkedAt": "2026-06-27T05:40:00.000Z"
},
"couriers": [
{ "slug": "jne", "name": "JNE" },
{ "slug": "anteraja", "name": "AnterAja" }
]
}
}| Field | Type | Description |
|---|---|---|
tracking | object | null | Persisted courier/AWB + deep link; null until a resi is attached |
snapshot | object | null | Live result; snapshot.found is false when upstream is unavailable |
snapshot.events | array | Journey legs, newest first; empty when not parseable |
couriers | array | { slug, name } options for the seller's courier dropdown |
Errors
| Status | Code | Cause |
|---|---|---|
403 | FORBIDDEN | User is not a party in this escrow |
404 | NOT_FOUND | Escrow doesn't exist |
Force a fresh upstream tracking check, bypassing the server cache.
Authentication: session cookie (buyer, seller, or admin only). Rate limit: one call per escrow every 5 minutes (couriers update scans on the order of hours, so a tighter window just hammers the upstream for no fresher data).
Response
200 OK
Same shape as GET /api/escrows/:id/tracking, with a freshly fetched snapshot.
Errors
| Status | Code | Cause |
|---|---|---|
400 | NO_TRACKING | Escrow has no courier/AWB attached yet |
403 | FORBIDDEN | User is not a party in this escrow |
404 | NOT_FOUND | Escrow doesn't exist |
429 | RATE_LIMITED | Refreshed within the last 5 minutes |
To attach tracking, the seller includes an optional { "courier": "<slug>", "tracking_number": "<awb>" } body when calling POST /api/escrows/:id/send to mark the item shipped. Both fields must be present together; omitting them ships without tracking (e.g. digital goods).
See also
- Quickstart — sign and create your first escrow
- Authentication — how HMAC link signing works
- Handling webhooks — preferred over polling
- Errors — all error codes