Escrow
PAYDER holds the buyer's money until your conditions are met, then pays the seller's PAYDER wallet, or refunds the buyer.
How it works
- The seller has a PAYDER account and gives you their 10-digit wallet ID. Check it with
GET /v1/accounts/:walletId. - You create an escrow. The buyer opens
payUrl, signs in to their own PAYDER account, and pays from their PAYDER wallet. Buyers without money in the wallet add it by card or transfer first, so card payments also land with PAYDER. - The money is now held by PAYDER. Both buyer and seller see that on PAYDER. The seller cannot spend it, and the buyer cannot take it back.
- When your conditions are met you call
release, or the buyer taps release, and the money goes to the seller's wallet. If the buyer reports nothing withinautoReleaseHours(default 72), it releases automatically. - If the deal fails, the buyer files a refund request in PAYDER. Release pauses and you get
escrow.disputed. You then callrefund, or a PAYDER admin decides.
Anyone can browse your catalogue without an account. Only buyers need a PAYDER account, at the moment of paying.
Create an escrow
| Field | Type | Notes |
|---|---|---|
reference | string, required | Your unique id (for example the order code). |
amount | integer, required | Kobo. Minimum 10,000 (₦100). |
currency | string, required | Always "NGN". |
seller.walletId | string, required | The seller's PAYDER wallet ID (10 digits). Live: it must exist, or 422 account_not_found. |
buyer.email | string | If set, only that PAYDER account can pay. |
feeAmount | integer | Your commission in kobo, taken from the seller's side on release. |
feeWalletId | string | Your own PAYDER wallet ID. Required with feeAmount; the commission is paid there. |
autoReleaseHours | integer | 1 to 720, default 72. |
conditions | string | Shown to both parties, for example "Release when the item is handed over". |
description, returnUrl, metadata | As for checkouts. |
curl -s $BASE/v1/escrows \
-H "Authorization: Bearer $KEY" -H "X-Merchant-Id: $MID" \
-H "Idempotency-Key: $(uuidgen)" -H "Content-Type: application/json" \
-d '{
"reference": "ORD-1001",
"amount": 1250000,
"currency": "NGN",
"description": "Laptop, order Q-1001",
"seller": { "walletId": "1234567890" },
"feeAmount": 62500,
"feeWalletId": "0987654321",
"conditions": "Release when the buyer receives the laptop.",
"returnUrl": "https://quadbay.example/orders/1001"
}'{ "id": "esc_8a2c...", "reference": "ORD-1001", "status": "pending_payment", "payUrl": "https://www.payder.ng/escrow/esc_8a2c..." }Other calls
| Call | What it does |
|---|---|
GET /v1/escrows/:reference | Status, amounts, who has paid. |
POST /v1/escrows/:reference/release | Conditions met: pay the seller (less your fee). |
POST /v1/escrows/:reference/refund | Send money back to the buyer. Body {"{ amount?, reason? }"}: with no amount it is a full refund; with an amount the buyer gets that and the rest goes to the seller. |
POST /v1/escrows/:reference/cancel | Cancel an unpaid escrow. A paid one must be refunded instead. |
GET /v1/accounts/:walletId | Does this PAYDER wallet exist? Returns a masked name. |
Statuses: pending_payment, funded, disputed, released, refunded, cancelled, expired. An unpaid escrow expires after 24 hours.
Webhook events
| Event | Meaning |
|---|---|
escrow.funded | The buyer paid. The money is held by PAYDER. |
escrow.disputed | The buyer asked for a refund. Release is paused. |
escrow.released | Money went to the seller (amountMinor is the seller's share before your fee). |
escrow.refunded | Money went back to the buyer (amountMinor is what they got). |
escrow.cancelled | Cancelled, or not paid in time. |
Linking a seller's wallet
Before paying a seller you can ask them to link their PAYDER wallet to your app. Nothing is linked until the wallet's owner approves it in the PAYDER app with their transaction PIN. You never see or handle the PIN.
| Call | What it does |
|---|---|
POST /v1/wallet-links | Body { linkRef, walletId, requesterName, expiresInMinutes (5-60) }. Returns 201 { linkId, linkRef, status: "PENDING", walletId, walletName (masked), expiresAt }. The same linkRef returns the same link (200). A newer request for the wallet cancels the older pending one. At most 5 requests per wallet per hour. |
GET /v1/wallet-links/:linkId | Status: PENDING, APPROVED, REJECTED, EXPIRED or REVOKED. Use it if a webhook is missed. |
DELETE /v1/wallet-links/:linkId | Unlink from your side. Fires wallet.link.revoked. |
GET /v1/wallets/:walletId/balance | Returns { availableMinor, heldMinor, currency }. Only for an APPROVED link, otherwise 403. |
POST /v1/sandbox/wallet-links/:linkId/approve | reject | Sandbox only: decide a request without a PIN. Sandbox test wallets: 0000000001, 0000000002, 0000000003. |
| Webhook event | Meaning |
|---|---|
wallet.link.approved | The owner approved with their PIN. walletName is the registered holder name. |
wallet.link.rejected | The owner declined. |
wallet.link.expired | Nobody decided in time. |
wallet.link.revoked | Unlinked by the owner or by you. |
Body: { id, event, reference (= linkId), linkId, linkRef, walletId, walletName, occurredAt }, signed like every other event. If your account has "require linked wallets" switched on, creating an escrow for a seller wallet that has not approved a link is refused with wallet_not_linked.
payUrl and press Pay (sandbox). Live escrows need real PAYDER accounts for the seller and buyer.