Skip to content

Payments and checking status ​

Most integrations send the buyer to the hosted checkout at https://rekberpay.com/escrow/<id>/pay and only poll the result (step 5). If you build your own checkout, these are the calls behind it. The run below pays an escrow of Rp250.000 with static QRIS, a manual method.

Escrow payments are not pushed to you as webhooks. Poll the status as shown in step 5.

1. List the payment methods ​

bash
curl https://api.rekberpay.com/v1/gateways/available-methods
js
const res = await fetch('https://api.rekberpay.com/v1/gateways/available-methods', {
});
const data = await res.json();
python
import os

import requests

resp = requests.get(
    "https://api.rekberpay.com/v1/gateways/available-methods",
    timeout=30,
)
print(resp.status_code, resp.json())
php
<?php

$ch = curl_init('https://api.rekberpay.com/v1/gateways/available-methods');
curl_setopt_array($ch, [
    CURLOPT_RETURNTRANSFER => true,
]);
$response = json_decode(curl_exec($ch), true);
$status = curl_getinfo($ch, CURLINFO_RESPONSE_CODE);
curl_close($ch);

200 OK

json
{
    "success": true,
    "data": [
        {
            "provider": "manual_qris",
            "display_name": "Manual QRIS (static)",
            "supported_methods": [
                "qris"
            ],
            "is_manual": true,
            "is_wallet": false,
            "fee_percent": 0,
            "fee_fixed": 0,
            "config": {
                "qris_image_url": "https://api.rekberpay.com/uploads/escrow/1779846015565_f14a5645.jpg"
            }
        },
        {
            "provider": "bank_transfer_manual",
            "display_name": "Bank Transfer (Manual)",
            "supported_methods": [
                "bank_transfer"
            ],
            "is_manual": true,
            "is_wallet": false,
            "fee_percent": 0,
            "fee_fixed": 0,
            "config": {
                "bank_name": "Bank Mandiri",
                "account_number": "1234567890",
                "account_holder": "PT RekberPay Indonesia"
            }
        }
    ]
}

supported_methods are the values for method. is_manual: true means RekberPay checks the transfer by hand, so the buyer must report it (step 3).

2. Start the payment ​

The buyer starts the payment. Send an Idempotency-Key so a retry never opens a second payment.

bash
curl -X POST https://api.rekberpay.com/v1/escrows/33333330-3333-4333-8333-333333333333/pay \
  -H "Authorization: Bearer $REKBERPAY_API_KEY" \
  -H "Idempotency-Key: 66666660-6666-4666-8666-666666666666" \
  -H "Content-Type: application/json" \
  -d '{
    "method": "qris"
  }'
js
const res = await fetch('https://api.rekberpay.com/v1/escrows/33333330-3333-4333-8333-333333333333/pay', {
    method: 'POST',
    headers: {
        Authorization: `Bearer ${process.env.REKBERPAY_API_KEY}`,
        'Content-Type': 'application/json',
        'Idempotency-Key': '66666660-6666-4666-8666-666666666666',
    },
    body: JSON.stringify({
        method: "qris"
    }),
});
const data = await res.json();
python
import os

import requests

resp = requests.post(
    "https://api.rekberpay.com/v1/escrows/33333330-3333-4333-8333-333333333333/pay",
    headers={
        "Authorization": f"Bearer {os.environ['REKBERPAY_API_KEY']}",
        "Idempotency-Key": "66666660-6666-4666-8666-666666666666",
    },
    json={
        "method": "qris",
    },
    timeout=30,
)
print(resp.status_code, resp.json())
php
<?php

$ch = curl_init('https://api.rekberpay.com/v1/escrows/33333330-3333-4333-8333-333333333333/pay');
curl_setopt_array($ch, [
    CURLOPT_CUSTOMREQUEST => 'POST',
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_HTTPHEADER => [
        'Authorization: Bearer ' . getenv('REKBERPAY_API_KEY'),
        'Content-Type: application/json',
        'Idempotency-Key: 66666660-6666-4666-8666-666666666666',
    ],
    CURLOPT_POSTFIELDS => json_encode([
        'method' => 'qris',
    ]),
]);
$response = json_decode(curl_exec($ch), true);
$status = curl_getinfo($ch, CURLINFO_RESPONSE_CODE);
curl_close($ch);

201 Created

json
{
    "success": true,
    "data": {
        "payment_link_id": "77777770-7777-4777-8777-777777777777",
        "gateway_order_id": "RBP-33333330-1791901398340-ba720621",
        "method": "qris",
        "provider": "manual_qris",
        "qr_code_url": "https://api.rekberpay.com/uploads/escrow/1779846015565_f14a5645.jpg",
        "expires_at": "2026-10-13T15:23:18.340Z",
        "amount": 250000,
        "escrow_amount": 250000,
        "surcharge": 0
    }
}

Show qr_code_url (or the bank details from step 1) until expires_at. Only the buyer may pay. If the buyer seat is still empty, the caller takes it first (with the same checks as joining) and pays as the buyer. The seller, or anyone once another user holds the buyer seat, gets this:

403 Forbidden

json
{
    "success": false,
    "error": {
        "code": "NOT_BUYER",
        "message": "Hanya pembeli di rekber ini yang bisa membayar."
    }
}

3. Report a manual transfer ​

After paying with a manual method, the buyer presses "Saya sudah bayar". The escrow moves to pending_review while RekberPay checks the money:

bash
curl -X POST https://api.rekberpay.com/v1/escrows/33333330-3333-4333-8333-333333333333/claim-payment \
  -H "Authorization: Bearer $REKBERPAY_API_KEY" \
  -H "Idempotency-Key: 88888880-8888-4888-8888-888888888888" \
  -H "Content-Type: application/json" \
  -d '{}'
js
const res = await fetch('https://api.rekberpay.com/v1/escrows/33333330-3333-4333-8333-333333333333/claim-payment', {
    method: 'POST',
    headers: {
        Authorization: `Bearer ${process.env.REKBERPAY_API_KEY}`,
        'Content-Type': 'application/json',
        'Idempotency-Key': '88888880-8888-4888-8888-888888888888',
    },
    body: JSON.stringify({}),
});
const data = await res.json();
python
import os

import requests

resp = requests.post(
    "https://api.rekberpay.com/v1/escrows/33333330-3333-4333-8333-333333333333/claim-payment",
    headers={
        "Authorization": f"Bearer {os.environ['REKBERPAY_API_KEY']}",
        "Idempotency-Key": "88888880-8888-4888-8888-888888888888",
    },
    json={},
    timeout=30,
)
print(resp.status_code, resp.json())
php
<?php

$ch = curl_init('https://api.rekberpay.com/v1/escrows/33333330-3333-4333-8333-333333333333/claim-payment');
curl_setopt_array($ch, [
    CURLOPT_CUSTOMREQUEST => 'POST',
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_HTTPHEADER => [
        'Authorization: Bearer ' . getenv('REKBERPAY_API_KEY'),
        'Content-Type: application/json',
        'Idempotency-Key: 88888880-8888-4888-8888-888888888888',
    ],
    CURLOPT_POSTFIELDS => json_encode(new stdClass()),
]);
$response = json_decode(curl_exec($ch), true);
$status = curl_getinfo($ch, CURLINFO_RESPONSE_CODE);
curl_close($ch);

200 OK

json
{
    "success": true,
    "data": {
        "id": "33333330-3333-4333-8333-333333333333",
        "buyer_id": "11111110-1111-4111-8111-111111111111",
        "seller_id": "44444440-4444-4444-8444-444444444444",
        "item_name": "Kamera Fujifilm X-T30 bekas",
        "amount": "250000.00",
        "description": "Body only, shutter count 8.000, lengkap dengan dus.",
        "category": "goods",
        "status": "pending_review",
        "counterparty_email": null,
        "counterparty_username": null,
        "source_url": null,
        "source_screenshot_url": null,
        "attachment_url": "https://api.rekberpay.com/uploads/escrow/1791901398283_862e8cf475cf7693efed4855a91995d0_7e10e097a2087a40e3e8e3b659df3f65.png",
        "deadline_at": null,
        "auto_release_at": null,
        "partner_ref": null,
        "partner_sig_version": null,
        "paid_at": null,
        "released_at": null,
        "refunded_at": null,
        "cancelled_at": null,
        "tracking_courier": null,
        "tracking_number": null,
        "tracking_status": null,
        "tracking_last_checked_at": null,
        "tracking_snapshot": null,
        "created_at": "2026-10-13T14:23:18.305Z",
        "updated_at": "2026-10-13T14:23:18.352Z",
        "user_role": "buyer"
    }
}

A second claim is refused:

409 Conflict

json
{
    "success": false,
    "error": {
        "code": "INVALID_STATE",
        "message": "Tidak dapat klaim pembayaran dari status: pending_review"
    }
}

4. RekberPay confirms ​

RekberPay staff check the transfer and confirm it. There is nothing to call here; the status changes on its own.

5. Poll the status ​

Poll every 5 seconds while the status is pending or awaiting_confirmation, and stop at paid, failed or expired. Before any payment is started the answer is 404 NO_PAYMENT, which is not a failure:

404 Not Found

json
{
    "success": false,
    "error": {
        "code": "NO_PAYMENT",
        "message": "No payment found for this escrow"
    }
}

While the buyer has not reported the transfer yet:

200 OK

json
{
    "success": true,
    "data": {
        "id": "77777770-7777-4777-8777-777777777777",
        "escrow_id": "33333330-3333-4333-8333-333333333333",
        "provider": "manual_qris",
        "method": "qris",
        "gateway_order_id": "RBP-33333330-1791901398340-ba720621",
        "amount": "250000.00",
        "status": "pending",
        "qr_url": "https://api.rekberpay.com/uploads/escrow/1779846015565_f14a5645.jpg",
        "va_number": null,
        "redirect_url": null,
        "expires_at": "2026-10-13T15:23:18.340Z",
        "raw_response": "{\"provider\":\"qris_manual\",\"amount\":250000,\"instructions\":\"Scan QRIS dengan aplikasi mobile banking / e-wallet, lalu tekan \\\"Saya sudah bayar\\\".\"}",
        "created_at": "2026-10-13T14:23:18.340Z",
        "updated_at": "2026-10-13T14:23:18.340Z",
        "paid_at": null
    }
}

After RekberPay confirmed it:

bash
curl https://api.rekberpay.com/v1/escrows/33333330-3333-4333-8333-333333333333/payment-status \
  -H "Authorization: Bearer $REKBERPAY_API_KEY"
js
const res = await fetch('https://api.rekberpay.com/v1/escrows/33333330-3333-4333-8333-333333333333/payment-status', {
    headers: {
        Authorization: `Bearer ${process.env.REKBERPAY_API_KEY}`,
    },
});
const data = await res.json();
python
import os

import requests

resp = requests.get(
    "https://api.rekberpay.com/v1/escrows/33333330-3333-4333-8333-333333333333/payment-status",
    headers={
        "Authorization": f"Bearer {os.environ['REKBERPAY_API_KEY']}",
    },
    timeout=30,
)
print(resp.status_code, resp.json())
php
<?php

$ch = curl_init('https://api.rekberpay.com/v1/escrows/33333330-3333-4333-8333-333333333333/payment-status');
curl_setopt_array($ch, [
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_HTTPHEADER => [
        'Authorization: Bearer ' . getenv('REKBERPAY_API_KEY'),
    ],
]);
$response = json_decode(curl_exec($ch), true);
$status = curl_getinfo($ch, CURLINFO_RESPONSE_CODE);
curl_close($ch);

200 OK

json
{
    "success": true,
    "data": {
        "id": "77777770-7777-4777-8777-777777777777",
        "escrow_id": "33333330-3333-4333-8333-333333333333",
        "provider": "manual_qris",
        "method": "qris",
        "gateway_order_id": "RBP-33333330-1791901398340-ba720621",
        "amount": "250000.00",
        "status": "paid",
        "qr_url": "https://api.rekberpay.com/uploads/escrow/1779846015565_f14a5645.jpg",
        "va_number": null,
        "redirect_url": null,
        "expires_at": "2026-10-13T15:23:18.340Z",
        "raw_response": "{\"provider\":\"qris_manual\",\"amount\":250000,\"instructions\":\"Scan QRIS dengan aplikasi mobile banking / e-wallet, lalu tekan \\\"Saya sudah bayar\\\".\"}",
        "created_at": "2026-10-13T14:23:18.340Z",
        "updated_at": "2026-10-13T14:23:18.364Z",
        "paid_at": "2026-10-13T14:23:18.364Z"
    }
}

A small polling loop in Node.js:

js
async function waitForPayment(escrowId, apiKey, { everyMs = 5000, timeoutMs = 120000 } = {}) {
    const until = Date.now() + timeoutMs;
    while (Date.now() < until) {
        const res = await fetch(`https://api.rekberpay.com/v1/escrows/${escrowId}/payment-status`, {
            headers: { Authorization: `Bearer ${apiKey}` },
        });
        if (res.status === 429) {
            await new Promise((r) => setTimeout(r, Number(res.headers.get('retry-after') ?? 5) * 1000));
            continue;
        }
        const body = await res.json();
        const status = res.status === 404 ? 'none' : body.data.status;
        if (['paid', 'failed', 'expired'].includes(status)) return status;
        await new Promise((r) => setTimeout(r, everyMs));
    }
    return 'still_pending';
}

Treat the order as paid only when the status is paid. A buyer who closes the tab has not necessarily failed to pay, so keep the order open and check again later.

The fee ​

The buyer pays the escrow amount plus any gateway surcharge. The RekberPay fee is taken from the seller's side on release. Show it before the escrow exists:

bash
curl "https://api.rekberpay.com/v1/escrows/fee-estimate?amount=250000" \
  -H "Authorization: Bearer $REKBERPAY_API_KEY"
js
const res = await fetch('https://api.rekberpay.com/v1/escrows/fee-estimate?amount=250000', {
    headers: {
        Authorization: `Bearer ${process.env.REKBERPAY_API_KEY}`,
    },
});
const data = await res.json();
python
import os

import requests

resp = requests.get(
    "https://api.rekberpay.com/v1/escrows/fee-estimate?amount=250000",
    headers={
        "Authorization": f"Bearer {os.environ['REKBERPAY_API_KEY']}",
    },
    timeout=30,
)
print(resp.status_code, resp.json())
php
<?php

$ch = curl_init('https://api.rekberpay.com/v1/escrows/fee-estimate?amount=250000');
curl_setopt_array($ch, [
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_HTTPHEADER => [
        'Authorization: Bearer ' . getenv('REKBERPAY_API_KEY'),
    ],
]);
$response = json_decode(curl_exec($ch), true);
$status = curl_getinfo($ch, CURLINFO_RESPONSE_CODE);
curl_close($ch);

200 OK

json
{
    "success": true,
    "data": {
        "gross": 250000,
        "fee": 7500,
        "net": 242500,
        "fee_type": "percentage",
        "fee_value": 3
    }
}

Next steps ​

Escrow, payment services and B2B deals for Indonesian businesses.