Skip to content

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.

What you need
  • A LINK_SIGNING_SECRET from 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)

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:

  1. Take the allowlisted fields: buyer, category, desc, expires, item, ref, seller, v. Drop any that are undefined or empty.
  2. Sort the remaining keys alphabetically.
  3. Format as key=encodeURIComponent(value) pairs joined by &.

Then HMAC-SHA256 the result with your LINK_SIGNING_SECRET and hex-encode it.

js
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
<?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);
python
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)
Note: amount is signed separately

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.

Keep the secret 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.

bash
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..."
  }'
js
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:

json
{
    "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:

http
GET /api/escrows/:id/payment-status
Authorization: Bearer <your-api-key>
json
{
    "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.

js
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.
Ready for production?

Move from sandbox to production by swapping LINK_SIGNING_SECRET and the base URL. Everything else stays identical.

Released under the proprietary RekberPay license. Built for Indonesian merchants.