Headless / Custom API
Build a fully custom payment experience using the BINKPAY REST API directly. This reference documents every endpoint you need to accept payments, issue refunds, manage invoices, and automate treasury operations — with examples in cURL and Node.js using the native fetch API.
Authentication
All API requests must include an Authorization header with a Bearer token. BINKPAY issues two types of secret API keys:
| Key type | Prefix | When to use |
|---|---|---|
| Live secret key | sk_live_… | Production — processes real payments. |
| Test secret key | sk_test_… | Development and CI — no money moves. Use test card 4242 4242 4242 4242. |
Retrieve your keys from Dashboard → Developer → API Keys. Rotate a compromised key immediately from the same page.
Keep your secret key private
Never expose sk_live_ or sk_test_ keys in client-side code, public repositories, or logs. Use environment variables and a secrets manager in production. Authorization header
curl https://api.binkpay.net/v1/payments \
-H "Authorization: Bearer sk_live_51HxK9..." \
-H "Content-Type: application/json"Base URLs
| Environment | Base URL | Currencies |
|---|---|---|
| Global | https://api.binkpay.net | USD, AED, EUR, GBP, SAR |
| Egypt | https://api.binkpay.eg | EGP only |
Environment currency restriction
The Egypt environment (api.binkpay.eg) only accepts EGP and Egypt-specific payment methods (Vodafone Cash, InstaPay, Fawry, ValU, MeezaPay). The Global environment (api.binkpay.net) does not accept EGP.
Rate limits & idempotency
Rate limits
Each API key is limited to 100 requests per second. Exceeding this limit returns a 429 Too Many Requests response. The Retry-After header in the response contains the number of seconds to wait before retrying.
HTTP/1.1 429 Too Many Requests
Retry-After: 1
X-RateLimit-Limit: 100
X-RateLimit-Remaining: 0
X-RateLimit-Reset: 1730734270
Idempotency
All POST requests that create or mutate resources accept an Idempotency-Key header. Send a UUID v4 as the key. If the request fails due to a network error and you retry with the same key, BINKPAY returns the original response without creating a duplicate resource. Keys expire after 24 hours.
Idempotent payment creation
curl -X POST https://api.binkpay.net/v1/payments \
-H "Authorization: Bearer sk_live_51HxK9..." \
-H "Content-Type: application/json" \
-H "Idempotency-Key: 7f6a1b3c-4e2d-41a8-b9f0-3c5e8d2a7f1b" \
-d '{
"amount": 4999,
"currency": "USD",
"payment_method": "card",
"return_url": "https://shop.example.com/checkout/return"
}'Payments
Base path: /v1/payments
/v1/paymentsInitiates a payment. For card and digital wallet payments (apple_pay, google_pay), BINKPAY returns a checkout_url to redirect the customer to the hosted payment page. For Egypt wallet methods (vodafone_cash, instapay, fawry, valu, meezapay), the customer receives an OTP or payment code on their device. All amounts are in the lowest denomination (piastres for EGP, cents for USD, fils for AED).
curl -X POST https://api.binkpay.net/v1/payments \
-H "Authorization: Bearer sk_live_51HxK9..." \
-H "Content-Type: application/json" \
-H "Idempotency-Key: 7f6a1b3c-4e2d-41a8-b9f0-3c5e8d2a7f1b" \
-d '{
"amount": 4999,
"currency": "USD",
"payment_method": "card",
"customer_id": "cus_01HX8FZRCPK7YN2D1L0JQWMAE",
"return_url": "https://shop.example.com/checkout/return",
"metadata": {
"order_id": "ORD-2025-00441",
"product_sku": "SKU-7734"
}
}'Response
{
"id": "pay_01HX9K2QVPM7AZ3E2M1JTXNBD",
"object": "payment",
"amount": 4999,
"amount_captured": 0,
"currency": "USD",
"status": "requires_action",
"payment_method": "card",
"customer_id": "cus_01HX8FZRCPK7YN2D1L0JQWMAE",
"description": null,
"metadata": {
"order_id": "ORD-2025-00441",
"product_sku": "SKU-7734"
},
"return_url": "https://shop.example.com/checkout/return",
"checkout_url": "https://checkout.binkpay.net/c/pay_01HX9K2QVPM7AZ3E2M1JTXNBD",
"livemode": true,
"created_at": "2025-11-04T14:31:55Z",
"updated_at": "2025-11-04T14:31:55Z"
}/v1/payments/:idcurl https://api.binkpay.net/v1/payments/pay_01HX9K2QVPM7AZ3E2M1JTXNBD \
-H "Authorization: Bearer sk_live_51HxK9..."Response
{
"id": "pay_01HX9K2QVPM7AZ3E2M1JTXNBD",
"object": "payment",
"amount": 4999,
"amount_captured": 4999,
"currency": "USD",
"status": "succeeded",
"payment_method": "card",
"customer_id": "cus_01HX8FZRCPK7YN2D1L0JQWMAE",
"billing_details": {
"name": "Alex Mercer",
"email": "alex@example.com",
"phone": "+12125550100",
"address": {
"line1": "123 Main St",
"city": "New York",
"state": "NY",
"postal_code": "10001",
"country": "US"
}
},
"metadata": {
"order_id": "ORD-2025-00441",
"product_sku": "SKU-7734"
},
"return_url": "https://shop.example.com/checkout/return",
"livemode": true,
"captured_at": "2025-11-04T14:32:09Z",
"created_at": "2025-11-04T14:31:55Z",
"updated_at": "2025-11-04T14:32:10Z"
}/v1/payments/:id/captureCapture an authorized payment
Captures a payment that is in authorized status. You may capture a partial amount by providing an amount less than the authorized amount. The uncaptured remainder is automatically released.
curl -X POST https://api.binkpay.net/v1/payments/pay_01HX9K2QVPM7AZ3E2M1JTXNBD/capture \
-H "Authorization: Bearer sk_live_51HxK9..." \
-H "Content-Type: application/json" \
-d '{
"amount": 4999
}'/v1/payments/:id/cancelCancels a payment that has not yet been captured. If the payment has already been captured, use the Refunds API instead.
curl -X POST https://api.binkpay.net/v1/payments/pay_01HX9K2QVPM7AZ3E2M1JTXNBD/cancel \
-H "Authorization: Bearer sk_live_51HxK9..." \
-H "Content-Type: application/json"Refunds
Base path: /v1/refunds
/v1/refundsCreates a refund for a captured payment. To issue a full refund omit the amount field. To issue a partial refund supply an amount less than payment.amount_captured. The reason field accepts duplicate, fraudulent, or requested_by_customer.
curl -X POST https://api.binkpay.net/v1/refunds \
-H "Authorization: Bearer sk_live_51HxK9..." \
-H "Content-Type: application/json" \
-H "Idempotency-Key: c3a7e9f1-2b4d-4e6a-8c0f-5d1b7e3a9c2e" \
-d '{
"payment_id": "pay_01HX9K2QVPM7AZ3E2M1JTXNBD",
"amount": 2000,
"reason": "requested_by_customer"
}'/v1/refunds/:idcurl https://api.binkpay.net/v1/refunds/ref_01HXB3NCPQ5YZ7WD2K0MJRTAE \
-H "Authorization: Bearer sk_live_51HxK9..."Invoices
Base path: /v1/invoices
/v1/invoicesCreates a draft invoice. Amounts per line item are in the smallest denomination. The invoice total is calculated as sum(amount × quantity) across all line items. Call /send to email the invoice to the customer.
curl -X POST https://api.binkpay.net/v1/invoices \
-H "Authorization: Bearer sk_live_51HxK9..." \
-H "Content-Type: application/json" \
-d '{
"customer_id": "cus_01HX8FZRCPK7YN2D1L0JQWMAE",
"currency": "USD",
"due_date": "2025-11-30",
"line_items": [
{
"description": "Pro subscription — November 2025",
"amount": 2900,
"quantity": 1
},
{
"description": "Additional workspace seat",
"amount": 900,
"quantity": 3
}
],
"metadata": {
"billing_period": "2025-11"
}
}'/v1/invoices/:idcurl https://api.binkpay.net/v1/invoices/inv_01HXBF3KCPM8YN4E2L1JRWTAE \
-H "Authorization: Bearer sk_live_51HxK9..."/v1/invoices/:id/sendEmails the invoice PDF to the customer's email address on record. Also transitions the invoice status from draft to sent.
curl -X POST https://api.binkpay.net/v1/invoices/inv_01HXBF3KCPM8YN4E2L1JRWTAE/send \
-H "Authorization: Bearer sk_live_51HxK9..." \
-H "Content-Type: application/json"/v1/invoices/:id/payMarks an invoice as paid outside of BINKPAY (e.g. cash, bank transfer). Use this to keep your invoice records accurate when payment was collected offline.
curl -X POST https://api.binkpay.net/v1/invoices/inv_01HXBF3KCPM8YN4E2L1JRWTAE/pay \
-H "Authorization: Bearer sk_live_51HxK9..." \
-H "Content-Type: application/json" \
-d '{ "payment_method": "cash" }'Customers
Base path: /v1/customers
/v1/customersCreates a customer object. Attach a customer_id to payments and invoices to associate them with this customer and enable saved payment methods.
curl -X POST https://api.binkpay.net/v1/customers \
-H "Authorization: Bearer sk_live_51HxK9..." \
-H "Content-Type: application/json" \
-d '{
"email": "alex@example.com",
"first_name": "Alex",
"last_name": "Mercer",
"phone": "+12125550100",
"metadata": {
"plan": "pro",
"internal_id": "user_8821"
}
}'/v1/customers/:idcurl https://api.binkpay.net/v1/customers/cus_01HX8FZRCPK7YN2D1L0JQWMAE \
-H "Authorization: Bearer sk_live_51HxK9..."/v1/customersQuery parameters: limit (1–100, default 20), page (default 1), email (exact match filter).
curl "https://api.binkpay.net/v1/customers?limit=20&page=1&email=alex%40example.com" \
-H "Authorization: Bearer sk_live_51HxK9..."Webhooks
Base path: /v1/webhooks
/v1/webhooksRegister a webhook endpoint
Registers a URL to receive signed event payloads. The response includes a secret (prefixed whsec_) that you use to verify incoming signatures. This secret is only returned once — store it securely.
Available events: payment.succeeded, payment.failed, refund.created, dispute.created, checkout.completed, invoice.paid, payout.completed.
curl -X POST https://api.binkpay.net/v1/webhooks \
-H "Authorization: Bearer sk_live_51HxK9..." \
-H "Content-Type: application/json" \
-d '{
"url": "https://app.example.com/webhooks/binkpay",
"events": [
"payment.succeeded",
"payment.failed",
"refund.created",
"dispute.created",
"checkout.completed",
"invoice.paid",
"payout.completed"
],
"description": "Production webhook — payments and payouts"
}'/v1/webhooksList webhook endpointsbash
curl https://api.binkpay.net/v1/webhooks \
-H "Authorization: Bearer sk_live_51HxK9..."/v1/webhooks/:idDelete a webhook endpointbash
curl -X DELETE https://api.binkpay.net/v1/webhooks/wh_01HXCK4NCPQ5YZ7WD2K0MJRTAE \
-H "Authorization: Bearer sk_live_51HxK9..."/v1/webhooks/:id/testDelivers a sample event payload to the registered URL so you can verify your handler before going live.
curl -X POST https://api.binkpay.net/v1/webhooks/wh_01HXCK4NCPQ5YZ7WD2K0MJRTAE/test \
-H "Authorization: Bearer sk_live_51HxK9..." \
-H "Content-Type: application/json" \
-d '{ "event": "payment.succeeded" }'Webhook signature verification
Every webhook delivery from BINKPAY includes an X-BinkPay-Signature header containing an HMAC-SHA256 hex digest of the raw request body, keyed with your webhook secret (whsec_…). Always verify this signature before processing the event.
Always verify signatures
Skipping signature verification exposes your application to spoofed events. An attacker could craft a fake payment.succeeded payload and trigger order fulfillment without a real payment.middleware/binkpay-webhook.ts
import crypto from 'node:crypto' ;
/**
* Express middleware that verifies the BINKPAY webhook signature.
* Mount BEFORE any body-parsing middleware so that req.body
* is the raw Buffer.
*/ export function binkpayWebhookMiddleware(webhookSecret) { return (req, res, next) => { const signature = req.headers[ 'x-binkpay-signature'];
if (!signature) {
return res.status( 400).json({ error: 'Missing X-BinkPay-Signature header' });
}
// req.body must be the raw Buffer — use express.raw({ type: '*/*' })
const rawBody = req.body;
if (!Buffer.isBuffer(rawBody)) { return res.status( 400).json({ error: 'Raw body required for signature verification' });
}
const expected = crypto
.createHmac( 'sha256' , webhookSecret)
.update(rawBody)
.digest( 'hex');
const isValid = crypto.timingSafeEqual(
Buffer. from(expected, 'hex'),
Buffer. from(signature, 'hex')
);
if (!isValid) {
return res.status( 403).json({ error: 'Invalid webhook signature' });
}
// Attach the parsed event to the request for downstream handlers req.binkpayEvent = JSON.parse(rawBody.toString( 'utf8'));
next();
};
}
// Usage in Express
import express from 'express';
const app = express();
app.post( '/webhooks/binkpay',
express.raw({ type: '*/*' }),
binkpayWebhookMiddleware(process.env.BINKPAY_WEBHOOK_SECRET),
(req, res) => { const event = req.binkpayEvent;
switch (event.type) {
case 'payment.succeeded':
// Fulfill order
break;
case 'refund.created':
// Update order status
break;
case 'dispute.created':
// Alert your support team break;
}
res.sendStatus( 200);
}
);Disputes
Base path: /v1/disputes
A dispute (chargeback) is created when a customer asks their card issuer to reverse a payment. You have a limited window — typically 7–14 days depending on the card network — to submit evidence. Monitor the dispute.created webhook event to be notified immediately.
/v1/disputesQuery parameters: status (needs_response | under_review | won | lost), limit, page.
curl "https://api.binkpay.net/v1/disputes?status=needs_response" \
-H "Authorization: Bearer sk_live_51HxK9..."/v1/disputes/:idcurl https://api.binkpay.net/v1/disputes/dis_01HXDP5NCPQ5YZ7WD2K0MJRTAE \
-H "Authorization: Bearer sk_live_51HxK9..."/v1/disputes/:id/evidenceSubmits evidence to counter the dispute. Once submitted, the status transitions to under_review. Evidence can only be submitted once per dispute — make sure all information is accurate before calling this endpoint.
curl -X POST https://api.binkpay.net/v1/disputes/dis_01HXDP5NCPQ5YZ7WD2K0MJRTAE/evidence \
-H "Authorization: Bearer sk_live_51HxK9..." \
-H "Content-Type: application/json" \
-d '{
"customer_email_address": "alex@example.com",
"customer_purchase_ip": "203.0.113.42",
"product_description": "Pro subscription — November 2025",
"receipt": "file_01HXE...",
"shipping_tracking_number": null,
"additional_evidence": "Customer confirmed via email on 2025-11-01 that they authorised this payment."
}'Treasury
Base path: /v1/treasury
/v1/treasury/balanceReturns available and pending balances per currency. available funds can be paid out immediately. pending funds are from recent payments still in the settlement window.
curl https://api.binkpay.net/v1/treasury/balance \
-H "Authorization: Bearer sk_live_51HxK9..."/v1/treasury/transactionsList treasury transactions
Query parameters: currency, type (payment | refund | payout | fee), limit, page, from / to (ISO 8601 date strings).
curl "https://api.binkpay.net/v1/treasury/transactions?currency=USD&limit=50" \
-H "Authorization: Bearer sk_live_51HxK9..."/v1/treasury/payoutsInitiates a bank transfer of available funds. Payouts typically settle within 1–3 business days. The payout.completed webhook fires when the transfer settles. Use an Idempotency-Key to prevent duplicate payouts.
curl -X POST https://api.binkpay.net/v1/treasury/payouts \
-H "Authorization: Bearer sk_live_51HxK9..." \
-H "Content-Type: application/json" \
-H "Idempotency-Key: a1b2c3d4-e5f6-7890-abcd-ef1234567890" \
-d '{
"amount": 500000,
"currency": "USD",
"destination": {
"type": "bank_account",
"account_number": "000123456789",
"routing_number": "110000000",
"account_holder_name": "Acme Corp LLC"
},
"description": "Weekly settlement — week 44"
}'Error handling
BINKPAY uses standard HTTP status codes. Every error response includes a JSON body with a machine-readable code, a human-readable message, and a request_id to quote when contacting support.
| Status | Meaning | Action |
|---|---|---|
| 400 | Bad Request | Fix the request body — a required field is missing or malformed. |
| 401 | Unauthorized | Check your Authorization header and API key. |
| 403 | Forbidden | The API key does not have permission for this operation. |
| 404 | Not Found | The resource ID does not exist in this environment. |
| 409 | Conflict | An idempotent request was replayed with a different body. |
| 422 | Unprocessable Entity | Request body is valid JSON but fails business rules (e.g. EGP on global env). |
| 429 | Too Many Requests | Respect the Retry-After header and back off. |
| 500 | Internal Server Error | An unexpected server error occurred. Retry with exponential backoff and contact support if persistent. |
Error response shape
Error response
Error codes
| Code | Description |
|---|---|
| payment_declined | The card issuer declined the payment. Ask the customer to use a different payment method. |
| insufficient_funds | The card or wallet does not have enough funds to cover the amount. |
| invalid_card | The card number, expiry, or CVV is incorrect. |
| card_expired | The card expiry date has passed. |
| do_not_honor | Generic decline from the issuer. Customer should contact their bank. |
| currency_not_supported | The requested currency is not supported in this environment. |
| amount_too_small | The amount is below the minimum allowed for this currency. |
| amount_too_large | The amount exceeds the maximum allowed for this currency or account tier. |
| duplicate_idempotency_key | An idempotent request was replayed with a different request body. |
| webhook_secret_not_found | The webhook secret could not be found for signature verification. |
SDKs & libraries
The official BINKPAY Node.js SDK wraps the REST API with TypeScript types, automatic retries with exponential backoff, idempotency key management, webhook signature verification helpers, and auto-pagination for list endpoints.
Install
npm install @binkpay/nodeUsage
import Binkpay from '@binkpay/node';
const binkpay = new Binkpay({
apiKey: process.env.BINKPAY_SECRET_KEY, // sk_live_... or sk_test_...
// baseUrl: 'https://api.binkpay.eg', // uncomment for Egypt environment
});
// Create a payment
const payment = await binkpay.payments.create({
amount: 4999,
currency: 'USD',
payment_method: 'card',
customer_id: 'cus_01HX8FZRCPK7YN2D1L0JQWMAE',
return_url: 'https://shop.example.com/checkout/return' ,
metadata: { order_id: 'ORD-2025-00441' },
});
// Retrieve a payment
const retrieved = await binkpay.payments.retrieve( 'pay_01HX9K2QVPM7AZ3E2M1JTXNBD');
// Create a refund
const refund = await binkpay.refunds.create({
payment_id: payment.id,
amount: 2000,
reason: 'requested_by_customer',
});
// List customers
const customers = await binkpay.customers.list({ limit: 20, page: 1 });
// Iterate pages automatically
for await ( const customer of binkpay.customers.listAutoPaging({ limit: 100 })) {
console.log(customer.email);
}OpenAPI specification
A machine-readable OpenAPI 3.1 spec is available at https://api.binkpay.net/v1/openapi.json. Use it to generate client SDKs in any language with tools like openapi-generator
or to import the collection directly into Postman or Insomnia.
Need help? Contact BINKPAY support • API status page • Full documentation
PreviousRate Limits & IdempotencyRead more →Was this page helpful?
