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 verify the webhook 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)
  • A public webhook URL (use ngrok for local development)

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. Listen for the webhook

The moment the gateway confirms the payment, RekberPay POSTs a webhook to your configured URL.

json
{
    "event": "payment.completed",
    "escrow_id": "7f8a9b0c-1d2e-3f4a-5b6c-7d8e9f0a1b2c",
    "ref": "ORDER_12345",
    "amount": 250000,
    "status": "paid",
    "timestamp": "2026-05-18T09:27:00.000Z",
    "signature": "a1b2c3..."
}

Verify the signature before trusting the payload. The signature is HMAC-SHA256(event + escrow_id + amount + status + timestamp, WEBHOOK_SECRET).

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.