Quickstart
Get from zero to a paid escrow in five minutes. We'll sign a partner link in your backend, create the escrow, redirect your buyer to pay, and confirm settlement on the way back.
- A
LINK_SIGNING_SECRETfrom RekberPay (request via [email protected]) - A seller account ID (a UUID — your platform's seller user as it exists in RekberPay)
- A backend that can compute HMAC-SHA256 (every modern language can)
1. Sign the link
The signature covers every parameter in the request. If anyone changes the amount, the item, or the seller after you sign it, the API rejects the call.
The canonical string is built by:
- Take the allowlisted fields:
buyer,category,desc,expires,item,ref,seller,v. Drop any that areundefinedor empty. - Sort the remaining keys alphabetically.
- Format as
key=encodeURIComponent(value)pairs joined by&.
Then HMAC-SHA256 the result with your LINK_SIGNING_SECRET and hex-encode it.
import crypto from 'node:crypto';
const SECRET = process.env.LINK_SIGNING_SECRET;
const ALLOWED = ['buyer', 'category', 'desc', 'expires', 'item', 'ref', 'seller', 'v'];
function signLink(params) {
const canonical = ALLOWED
.filter((k) => params[k] !== undefined && params[k] !== '')
.map((k) => `${k}=${encodeURIComponent(String(params[k]))}`)
.join('&');
return crypto.createHmac('sha256', SECRET).update(canonical).digest('hex');
}
const params = {
seller: '0a1b2c3d-4e5f-6a7b-8c9d-0e1f2a3b4c5d',
item: 'Jasa Logo Design',
category: 'service',
expires: Math.floor(Date.now() / 1000) + 7 * 24 * 60 * 60, // 7 days
ref: 'ORDER_12345',
v: 1,
};
params.sig = signLink(params);<?php
$SECRET = getenv('LINK_SIGNING_SECRET');
$ALLOWED = ['buyer', 'category', 'desc', 'expires', 'item', 'ref', 'seller', 'v'];
function sign_link(array $params, string $secret, array $allowed): string {
$parts = [];
foreach ($allowed as $k) {
if (isset($params[$k]) && $params[$k] !== '') {
$parts[] = $k . '=' . rawurlencode((string) $params[$k]);
}
}
return hash_hmac('sha256', implode('&', $parts), $secret);
}
$params = [
'seller' => '0a1b2c3d-4e5f-6a7b-8c9d-0e1f2a3b4c5d',
'item' => 'Jasa Logo Design',
'category' => 'service',
'expires' => time() + 7 * 24 * 60 * 60,
'ref' => 'ORDER_12345',
'v' => 1,
];
$params['sig'] = sign_link($params, $SECRET, $ALLOWED);import hmac, hashlib, os, time
from urllib.parse import quote
SECRET = os.environ['LINK_SIGNING_SECRET'].encode()
ALLOWED = ['buyer', 'category', 'desc', 'expires', 'item', 'ref', 'seller', 'v']
def sign_link(params: dict) -> str:
parts = [
f"{k}={quote(str(params[k]), safe='')}"
for k in ALLOWED
if params.get(k) not in (None, '')
]
return hmac.new(SECRET, '&'.join(parts).encode(), hashlib.sha256).hexdigest()
params = {
'seller': '0a1b2c3d-4e5f-6a7b-8c9d-0e1f2a3b4c5d',
'item': 'Jasa Logo Design',
'category': 'service',
'expires': int(time.time()) + 7 * 24 * 60 * 60,
'ref': 'ORDER_12345',
'v': 1,
}
params['sig'] = sign_link(params)amount is in the request body, not the signed canonical string. This keeps links shareable as URLs (one signed link can be reused for the same item with the same partner reference). The API still validates amount against the seller's escrow draft on the server side.
Never put LINK_SIGNING_SECRET in your frontend bundle. It's the equivalent of an API key — anyone with it can mint escrows on your behalf. Sign on your backend, return the signed payload to the browser, then redirect.
2. Create the escrow
POST the signed payload to /api/escrows/from-link. Always include Idempotency-Key so retries don't create duplicates.
curl -X POST https://api.rekberpay.com/api/escrows/from-link \
-H "Content-Type: application/json" \
-H "Idempotency-Key: 550e8400-e29b-41d4-a716-446655440000" \
-d '{
"seller": "0a1b2c3d-4e5f-6a7b-8c9d-0e1f2a3b4c5d",
"item": "Jasa Logo Design",
"category": "service",
"amount": 250000,
"expires": 1748448000,
"ref": "ORDER_12345",
"v": 1,
"sig": "f3a9c1..."
}'import { randomUUID } from 'node:crypto';
const res = await fetch('https://api.rekberpay.com/api/escrows/from-link', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Idempotency-Key': randomUUID(),
},
body: JSON.stringify(params),
});
const { data } = await res.json();
console.log('Redirect buyer to:', data.escrow_url);A successful response looks like this:
{
"success": true,
"data": {
"id": "7f8a9b0c-1d2e-3f4a-5b6c-7d8e9f0a1b2c",
"status": "pending_payment",
"escrow_url": "https://rekberpay.com/escrow/7f8a9b0c-1d2e-3f4a-5b6c-7d8e9f0a1b2c/pay",
"expires_at": "2026-05-25T07:00:00.000Z"
}
}3. Redirect the buyer
Send the buyer to data.escrow_url. They'll see your branded checkout (logo, seller name, item description, total) and pick a payment method: QRIS, bank transfer, GoPay, ShopeePay, Crypto.
You don't have to host anything — the page is rendered by RekberPay and returns to the URL you provided in your partner config when payment completes.
4. Poll for payment completion
Webhooks are not available yet
Outbound partner webhooks are not implemented — see Webhooks. Poll payment-status instead.
Once the buyer returns, poll until the payment settles:
GET /api/escrows/:id/payment-status
Authorization: Bearer <your-api-key>{
"success": true,
"data": {
"escrow_id": "7f8a9b0c-1d2e-3f4a-5b6c-7d8e9f0a1b2c",
"status": "paid",
"provider": "midtrans",
"amount": 250000
}
}The response is the most recent payment link for that escrow. Two cases to handle:
404 NO_PAYMENT— no payment attempt exists yet. Not an error; the buyer simply hasn't started checkout.403 FORBIDDEN— you are not a party to this escrow. Only the buyer, seller, or an admin may read it.
Poll every 5–10 seconds while the checkout is open, then back off. When status reaches paid, the money is held and you can fulfil.
For the authoritative full state — including timestamps and the escrow lifecycle status — use GET /api/escrows/:id.
import crypto from 'node:crypto';
function verifyWebhook(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'),
);
}Return 200 OK quickly (under 5 seconds). Process the work async — we'll retry up to 3 times on non-2xx responses with exponential backoff.
You're done
That's the whole integration. The buyer paid, the funds are in escrow, and your system has been notified.
Next, depending on your flow:
- Goods marketplace — read Creating escrows to understand the seller-ships → buyer-confirms flow that releases funds.
- Service marketplace — funds release on buyer confirmation by default, or after a configurable SLA timer (auto-release).
- Disputes — see Handling webhooks for the events fired when a buyer or seller opens a dispute.
Move from sandbox to production by swapping LINK_SIGNING_SECRET and the base URL. Everything else stays identical.