Merchant API

BRPAY Merchant API Integration Guide

API: Merchant API · Java / PHP SDK: see the delivery package for its version · Updated: 2026-11-06

1. Quick start

Use this API for game recharges and withdrawals. Orders, balances, ledger entries and callbacks use BRC. The platform values 1 BRC at 1 BRL; this API transfers BRC between BRPAY wallets. Domains, accounts, quantities and fees in this guide are examples.

Ask BRPAY for these server-side credentials and settings:

Parameter Description
baseUrl Merchant API HTTPS origin, without a path, query or trailing slash. Use the origin supplied by BRPAY.
merchantId Your assigned merchant number: uppercase M + 8 uppercase letters or digits.
signingSecret 32–512 UTF-8 bytes. Keep it on your server for API signing and callback verification.
  1. Get set up. Ask BRPAY for the three settings above and provide notifyUrl and returnUrl in each order request as needed.
  2. Check your balance first. Synchronize your server clock and run the signed request example. Once you get availableBalance, test orders in your test environment.
  3. Try each flow. Recharge: save your order → create a PAYIN → open cashierUrl → confirm payment by query or verified callback before crediting the game account. Withdrawal: save your order → verify the recipient wallet → create a PAYOUT → confirm receipt on SUCCESS.
  4. Try three failure cases. An expired PAYIN is never credited. A repeated callback is handled once. After a submission timeout, query the original order number first.
Function Path SDK method
Create PAYIN /api/merchant/orders/payin payin(...)
Create PAYOUT /api/merchant/orders/payout payout(...)
Query order /api/merchant/orders/query queryOrder(...) / querySystemOrder(...)
Query availability /api/merchant/orders/availability queryAvailability(...)
Query merchant balance /api/merchant/account/balance queryBalance(...)
Query merchant transactions /api/merchant/account/transactions queryTransactions(...)
Look up wallet /api/merchant/wallets/query queryWallet(...)

2. Common protocol

2.1 Request format

Item Rule
Protocol / method HTTPS / POST; loopback HTTP is allowed only for local development
Content-Type application/json; charset=utf-8
Encoding / body limit UTF-8 / 8192 bytes; JSON object only; duplicate keys and unknown fields rejected
Quantities Positive decimal string, at most 17 integer digits after leading zeros, exactly representable in cents. "00030.1200" means "30.12". No JSON numbers, exponents or fractional cents.
Time UTC RFC3339 ending in Z, e.g. 2026-11-05T04:00:00Z
Evolution Ignore added optional response/callback fields

Every endpoint requires these headers:

Header Format Description
X-Merchant-Id Uppercase M followed by eight uppercase letters or digits Platform merchant number
X-Timestamp 10-digit Unix seconds Current time, with 300 seconds of clock tolerance
X-Request-Id Lowercase UUID Generate a new value for each HTTP request. Keep your business order number unchanged for the same order.
X-Signature 64 lowercase hex characters HMAC-SHA256; duplicate authentication headers rejected

2.2 Request signature

Sign on your server. Encode the JSON as UTF-8 and hash those bytes with SHA-256. Send the same bytes you hashed. Join the seven lines below with \n (LF), with no trailing newline:

BP-MERCHANT
POST
<full-request-path>
<merchantId>
<epochSeconds>
<requestId>
<sha256Hex(rawBodyBytes)>

<full-request-path> is the actual path, such as /api/merchant/orders/payin, without a host, query string or extra slash. Do not parse and reformat the JSON before hashing it or substitute Windows CRLF for LF in the signature input.

Runnable example: sign a read-only balance query

Save this as merchant-balance.mjs. Use Node.js with built-in fetch (18+), set BRPAY_BASE_URL, BRPAY_MERCHANT_ID and BRPAY_SIGNING_SECRET on the server, and run node merchant-balance.mjs. It prints the available BRC balance. Use the new API origin supplied by BRPAY; local port 8109 serves the DEMO pages.

import { createHash, createHmac, randomUUID } from 'node:crypto';

const { BRPAY_BASE_URL, BRPAY_MERCHANT_ID, BRPAY_SIGNING_SECRET } = process.env;
if (!BRPAY_BASE_URL || !/^M(?!0{8}$)[0-9A-Z]{8}$/.test(BRPAY_MERCHANT_ID ?? '') ||
    !BRPAY_SIGNING_SECRET) {
  throw new Error('Set BRPAY_BASE_URL, BRPAY_MERCHANT_ID and BRPAY_SIGNING_SECRET');
}
const origin = new URL(BRPAY_BASE_URL);
const loopback = origin.protocol === 'http:' &&
  ['127.0.0.1', 'localhost', '[::1]'].includes(origin.hostname);
if ((!loopback && origin.protocol !== 'https:') || origin.pathname !== '/' ||
    origin.search || origin.hash || origin.username || origin.password) {
  throw new Error('BRPAY_BASE_URL must be the API origin (HTTPS or loopback HTTP)');
}
const path = '/api/merchant/account/balance';
const body = Buffer.from(JSON.stringify({}), 'utf8');
const timestamp = String(Math.floor(Date.now() / 1000));
const requestId = randomUUID();
const bodyHash = createHash('sha256').update(body).digest('hex');
const text = ['BP-MERCHANT', 'POST', path, BRPAY_MERCHANT_ID,
  timestamp, requestId, bodyHash].join('\n');
const signature = createHmac('sha256', BRPAY_SIGNING_SECRET).update(text, 'utf8').digest('hex');

const response = await fetch(origin.origin + path, {
  method: 'POST',
  headers: {
    'Content-Type': 'application/json; charset=utf-8',
    'X-Merchant-Id': BRPAY_MERCHANT_ID,
    'X-Timestamp': timestamp,
    'X-Request-Id': requestId,
    'X-Signature': signature
  },
  body,
  redirect: 'error',
  signal: AbortSignal.timeout(30_000)
});
const reply = await response.json();
if (response.status !== 200 || reply?.success !== true || reply.code !== 'OK' ||
    reply.requestId !== requestId || reply.data?.currency !== 'BRC' ||
    typeof reply.data.availableBalance !== 'string') {
  throw new Error(`HTTP ${response.status}; code=${reply?.code ?? '(missing)'}; requestId=${requestId}`);
}
console.log(reply.data.availableBalance);

Fixed signing values. Compare these with the body hash and signature produced by your own code.

signingSecret = example-only-signing-secret-32-bytes

BP-MERCHANT
POST
/api/merchant/orders/payin
M00000325
1793851200
aaaaaaaa-bbbb-4ccc-8ddd-eeeeeeeeeeee
74cbca80326f6565ae3a2f5c16607f96b8f42d13e30b7c99290ef75c8a05c07a

X-Signature: 8c5374d38568c2c84d1ca15d101b6c902f32398f83a45f9edbc30ac45b7b5c8c

Complete HTTP request for offline signing checks. Use the sample secret above. The body is the single line of JSON below, with no trailing newline. The fixed timestamp is for checking the signature only.

POST /api/merchant/orders/payin HTTP/1.1
Host: api.example.com
Content-Type: application/json; charset=utf-8
X-Merchant-Id: M00000325
X-Timestamp: 1793851200
X-Request-Id: aaaaaaaa-bbbb-4ccc-8ddd-eeeeeeeeeeee
X-Signature: 8c5374d38568c2c84d1ca15d101b6c902f32398f83a45f9edbc30ac45b7b5c8c
Content-Length: 86

{"merchantOrderNo":"RECHARGE-001","orderAmount":"30.00","description":"Game recharge"}

2.3 Response envelope

A successful response has HTTP 200, success: true and code: "OK". Each endpoint below shows a full response. Its business fields are under data. For an order request, HTTP 200 means the request was handled; read data.status to see whether funds moved.

On failure, success is false and data is null. Use the HTTP status and code in your program. message is for people to read. requestId echoes your request's trace ID (it may be null if omitted); share it and your order number with BRPAY when troubleshooting. timestamp is the UTC time when BRPAY built the response.

For example, an incorrect signature returns HTTP 401 / INVALID_SIGNATURE.

{
  "success": false,
  "code": "INVALID_SIGNATURE",
  "message": "Invalid signature",
  "requestId": "99999999-aaaa-4bbb-8ccc-dddddddddddd",
  "timestamp": "2026-11-05T04:06:00Z",
  "data": null
}

3. Orders, fees and states

The order response, the payment result and callback delivery are separate. A new PAYIN normally returns PENDING and has not debited the payer. A PAYOUT returning SUCCESS has credited the recipient. notificationStatus describes delivery only; EXHAUSTED does not change the payment result.

Flow Customer BRC balance Merchant BRC balance How to confirm success
PAYIN Debited by the order quantity at payment Credited with the order quantity minus the fee An order query returns SUCCESS, or a signed success callback is verified
PAYOUT Credited with the order quantity Debited by the order quantity plus the fee A successful payout response, an order query returning SUCCESS, or a verified success callback

Fee = the greater of the minimum fee and (order quantity × fee rate + fixed fee), rounded to two decimal places using HALF_UP. A PAYIN fee must be less than the order quantity. The fee is fixed when the order is created. Use Query availability to see current fees and allowed quantities.

Your order number identifies one business order and cannot be reused across PAYIN and PAYOUT. On ORDER_EXISTS, query the original order and check its direction, quantity and payout recipient. A conflict does not confirm that this request succeeded.

4. Endpoints

Merchant PAYIN orders use PENDING (awaiting or processing payment), SUCCESS (merchant payment committed), and FAILED (a confirmed terminal failure). failureCode is required for FAILED; expiry currently uses ORDER_EXPIRED. It is null for other states. Corrections use ORDER_CORRECTED. Initial terminal events: ORDER_SUCCEEDED or ORDER_FAILED. Wallet top-up success/failure, insufficient funds and cancellation of automatic-payment authorization do not terminate a merchant order that remains payable. A request timeout is not an order failure: query the original order. Rejected requests that create no order return an API error without an order callback.

4.1 Create PAYIN

POST /api/merchant/orders/payin · SDK payin(...)

Call this when a customer wants to top up a game account. Save both order numbers and send the customer to cashierUrl (or use appUrl to open the native app). Creating the order does not move funds. Credit the game account after verifying and matching an ORDER_SUCCEEDED callback, or confirming SUCCESS through the order query.

The complete minimal PAYIN body is {"merchantOrderNo":"RECHARGE-001","orderAmount":"30.00"}.

Callback URLs: Optionally provide notifyUrl and returnUrl in this request. Merchant profiles no longer configure a callback URL. See Result callbacks.

Request

Request field Type Required Description
merchantOrderNo string Yes Your reference for this business order. Use 1–64 case-sensitive characters matching [A-Za-z0-9][A-Za-z0-9._-]{0,63}. Use one number per business order across both PAYIN and PAYOUT.
orderAmount string Yes Positive decimal string, at most 17 integer digits after leading zeros, exactly representable in cents. "00030.1200" means "30.12". No JSON numbers, exponents or fractional cents.
description string / null No Optional business description. Omit it or send an empty string; omission is stored as an empty string. Up to 160 UTF-16 code units, with no ASCII control characters. null also means no description.
notifyUrl string / null No Asynchronous notification URL; omitted means no notification. Full HTTPS URL, max 512 characters.
returnUrl string / null No Synchronous merchant return page; omitted means no return action. Full HTTPS URL, max 512 characters.
{
  "merchantOrderNo": "RECHARGE-001",
  "orderAmount": "30.00",
  "description": "Game recharge",
  "notifyUrl": "https://merchant.example.com/webhooks/brpay",
  "returnUrl": "https://merchant.example.com/orders/RECHARGE-001"
}

Success response data

A new order normally starts at PENDING. The example uses a 1% fee plus 0.10 BRC. Use expiresAt for the deadline; stop directing the customer to checkout after expiry.

Response field Type Description
orderNo string BRPAY collection order number: RC + 14 digits.
merchantOrderNo string Your order number from the original request.
direction string Always PAYIN.
currency string Always BRC.
orderAmount string BRC the customer should pay.
feeAmount string Fee fixed at order creation, in BRC. Can be "0.00".
merchantAmount string Your net credit on success: orderAmount - feeAmount, in BRC.
actualAmount string / null null while awaiting payment; the BRC actually debited from the customer on success.
status string Starts at PENDING (awaiting payment). Confirm the outcome by query or callback.
failureCode string / null Failure reason; required for FAILED. Current expiry code: ORDER_EXPIRED. Null otherwise.
walletId string / null null while awaiting payment; payer's wallet account (W + 8 uppercase letters or digits) on success.
createdAt string Order creation time in UTC.
expiresAt string Payment deadline in UTC. The order cannot be paid after expiry.
completedAt string / null null while pending; query the order for its terminal timestamp.
orderRevision integer Result revision, initially 0. Each correction increments it.
notificationStatus string / null null while awaiting payment; no result callback exists yet.
cashierUrl string BRPAY checkout URL for Web payment, opening the app, or scanning the checkout QR code in the app.
appUrl string Native app payment link: brazilpay://cashier/<token>. The 22-character Base64URL token matches cashierUrl.

Full response example · HTTP 200

{
  "success": true,
  "code": "OK",
  "message": "",
  "requestId": "aaaaaaaa-bbbb-4ccc-8ddd-eeeeeeeeeeee",
  "timestamp": "2026-11-05T04:00:00Z",
  "data": {
    "orderNo": "RC00000000128364",
    "merchantOrderNo": "RECHARGE-001",
    "direction": "PAYIN",
    "currency": "BRC",
    "orderAmount": "30.00",
    "feeAmount": "0.40",
    "merchantAmount": "29.60",
    "actualAmount": null,
    "status": "PENDING",
    "walletId": null,
    "createdAt": "2026-11-05T04:00:00Z",
    "expiresAt": "2026-11-05T04:30:00Z",
    "completedAt": null,
    "orderRevision": 0,
    "notificationStatus": null,
    "cashierUrl": "https://payments.example.com/pay/aaaaaaaaaaaaaaaaaaaaaA",
    "appUrl": "brazilpay://cashier/aaaaaaaaaaaaaaaaaaaaaA",
    "failureCode": null
  }
}

If the request fails: On ORDER_EXISTS, timeout or an invalid receipt, query the original merchant order number first. For an invalid quantity, check the allowed range and fees.

4.2 Create PAYOUT

POST /api/merchant/orders/payout · SDK payout(...)

Call this to pay a withdrawal into a BRPAY wallet. First look up the wallet, require exists=true and canReceive=true, and confirm the intended recipient. HTTP 200 means the order was accepted as PENDING and the principal plus fee was reserved. Only SUCCESS confirms receipt; FAILED releases the hold. Rejected requests that create no order reserve no funds.

Callback URLs: Optionally provide notifyUrl and returnUrl in this request. Merchant profiles no longer configure a callback URL. See Result callbacks.

Request

Request field Type Required Description
merchantOrderNo string Yes Your reference for this business order. Use 1–64 case-sensitive characters matching [A-Za-z0-9][A-Za-z0-9._-]{0,63}. Use one number per business order across both PAYIN and PAYOUT.
orderAmount string Yes Positive decimal string, at most 17 integer digits after leading zeros, exactly representable in cents. "00030.1200" means "30.12". No JSON numbers, exponents or fractional cents.
recipientWalletId string Yes Use walletId from wallet lookup: W + 8 uppercase letters or digits.
description string / null No Optional business description. Omit it or send an empty string; omission is stored as an empty string. Up to 160 UTF-16 code units, with no ASCII control characters. null also means no description.
notifyUrl string / null No Asynchronous notification URL; omitted means no notification. Full HTTPS URL, max 512 characters.
returnUrl string / null No Synchronous merchant return page; omitted means no return action. Full HTTPS URL, max 512 characters.
{
  "merchantOrderNo": "WITHDRAW-001",
  "orderAmount": "20.00",
  "description": "Game withdrawal",
  "recipientWalletId": "W00012836",
  "notifyUrl": "https://merchant.example.com/webhooks/brpay",
  "returnUrl": "https://merchant.example.com/orders/WITHDRAW-001"
}

Success response data

The example uses a 2% fee plus 0.20 BRC. Acceptance returns PENDING; the example below is a final successful query. A confirmed SUCCESS is enough to confirm receipt; no need to wait for a callback. PAYOUT does not return cashierUrl or appUrl.

Response field Type Description
orderNo string BRPAY payout order number: PY + 14 digits.
merchantOrderNo string Your order number from the original request.
direction string Always PAYOUT.
currency string Always BRC.
orderAmount string BRC the recipient should receive.
feeAmount string Fee fixed at order creation, in BRC. Can be "0.00".
merchantAmount string The amount debited from your balance: orderAmount + feeAmount, in BRC.
actualAmount string / null Credited principal on SUCCESS; null on PENDING or FAILED.
status string PENDING accepted and processing; SUCCESS credited; FAILED failed and hold released.
failureCode string / null Failure reason on FAILED; null otherwise.
walletId string Designated recipient wallet in every state (W + 8 uppercase letters or digits), matching recipientWalletId. Only SUCCESS confirms credit.
createdAt string Order creation time in UTC.
expiresAt null Always null. There is no payout payment deadline.
completedAt string / null Terminal completion time in UTC; null on PENDING.
orderRevision integer Result revision, initially 0. Each correction increments it.
notificationStatus string / null PENDING awaiting delivery or retry, DELIVERED acknowledged with 2xx, or EXHAUSTED after automatic attempts end. Independent of payment status.

Full response example · HTTP 200

{
  "success": true,
  "code": "OK",
  "message": "",
  "requestId": "22222222-3333-4444-8555-666666666666",
  "timestamp": "2026-11-05T04:05:00Z",
  "data": {
    "orderNo": "PY00000000128365",
    "merchantOrderNo": "WITHDRAW-001",
    "direction": "PAYOUT",
    "currency": "BRC",
    "orderAmount": "20.00",
    "feeAmount": "0.60",
    "merchantAmount": "20.60",
    "actualAmount": "20.00",
    "status": "SUCCESS",
    "walletId": "W00012836",
    "createdAt": "2026-11-05T04:05:00Z",
    "expiresAt": null,
    "completedAt": "2026-11-05T04:05:00Z",
    "failureCode": null,
    "orderRevision": 0,
    "notificationStatus": "PENDING"
  }
}

If the request fails: For WALLET_NOT_FOUND, ACCOUNT_DISABLED or WALLET_LIMIT_EXCEEDED, check the recipient wallet. For MERCHANT_INSUFFICIENT_BALANCE, check your balance. On ORDER_EXISTS, timeout or an invalid receipt, query the original order number and confirm the direction, quantity and recipient.

4.3 Query order

POST /api/merchant/orders/query · SDK queryOrder(...) (merchant order number) / querySystemOrder(...) (BRPAY order number)

Look up an order belonging to the authenticated merchant by your reference or the BRPAY order number. Use this when a callback is missing, after an order request times out, or for reconciliation. Read status for the payment outcome and notificationStatus for callback delivery.

Request

Request field Type Required Description
merchantOrderNo string One of the two Original merchant order number: 1–64 case-sensitive characters matching [A-Za-z0-9][A-Za-z0-9._-]{0,63}.
orderNo string One of the two BRPAY order number: RC for PAYIN or PY for PAYOUT, followed by 14 digits.

Send one of these two request bodies:

{
  "merchantOrderNo": "RECHARGE-001"
}
{
  "orderNo": "RC00000000128364"
}

Success response data

The example shows a paid PAYIN with an acknowledged callback. All fields below are present except the two checkout links, which are returned only for PAYIN. A terminal PAYIN can still carry the links; use them only while PENDING.

Response field Type Description
orderNo string BRPAY order number: RC for PAYIN or PY for PAYOUT, followed by 14 digits.
merchantOrderNo string Your order number from the original request.
direction string PAYIN or PAYOUT.
currency string Always BRC.
orderAmount string Original order quantity in BRC.
feeAmount string Fee fixed at order creation, in BRC. Can be "0.00".
merchantAmount string Positive BRC quantity: net credit on PAYIN success or total PAYOUT debit including the fee.
actualAmount string / null On SUCCESS, BRC actually debited from the customer (PAYIN) or credited to the recipient (PAYOUT); otherwise null.
status string PAYIN: PENDING (awaiting payment), SUCCESS (paid), or FAILED. PAYOUT: PENDING (processing), SUCCESS (credited), or FAILED (failed and hold released).
failureCode string / null Failure reason; required for FAILED. Current expiry code: ORDER_EXPIRED. Null otherwise.
walletId string / null PAYOUT: designated recipient wallet in every state. PAYIN: actual payer when known (a corrected failure may retain the payer), otherwise null. Format: W + 8 uppercase letters or digits.
createdAt string Order creation time in UTC.
expiresAt string / null PAYIN payment deadline in UTC; null for PAYOUT.
completedAt string / null Completion time in UTC on success; equal to expiresAt on expiry; null while pending.
orderRevision integer Result revision, initially 0. Each correction increments it.
notificationStatus string / null PENDING awaiting delivery or retry, DELIVERED acknowledged with 2xx, EXHAUSTED after automatic attempts end, or null if no notification record exists yet.
cashierUrl string PAYIN only. BRPAY checkout URL; use only while awaiting payment.
appUrl string PAYIN only. Native app payment link with the same token as cashierUrl.

Full response example · HTTP 200

{
  "success": true,
  "code": "OK",
  "message": "",
  "requestId": "33333333-4444-4555-8666-777777777777",
  "timestamp": "2026-11-05T04:03:00Z",
  "data": {
    "orderNo": "RC00000000128364",
    "merchantOrderNo": "RECHARGE-001",
    "direction": "PAYIN",
    "currency": "BRC",
    "orderAmount": "30.00",
    "feeAmount": "0.40",
    "merchantAmount": "29.60",
    "actualAmount": "30.00",
    "status": "SUCCESS",
    "walletId": "W00012836",
    "createdAt": "2026-11-05T04:00:00Z",
    "expiresAt": "2026-11-05T04:30:00Z",
    "completedAt": "2026-11-05T04:02:00Z",
    "orderRevision": 0,
    "notificationStatus": "DELIVERED",
    "cashierUrl": "https://payments.example.com/pay/aaaaaaaaaaaaaaaaaaaaaA",
    "appUrl": "brazilpay://cashier/aaaaaaaaaaaaaaaaaaaaaA",
    "failureCode": null
  }
}

No order found: HTTP 404 / ORDER_NOT_FOUND. If recovering from a timed-out order request, follow Unconfirmed outcomes; a single 404 does not rule out a request that is still processing.

Full response when the order is missing · HTTP 404

{
  "success": false,
  "code": "ORDER_NOT_FOUND",
  "message": "Record not found",
  "requestId": "33333333-4444-4555-8666-888888888888",
  "timestamp": "2026-11-05T04:06:10Z",
  "data": null
}

4.4 Query availability

POST /api/merchant/orders/availability · SDK queryAvailability(...)

Check whether orders are currently accepted and get the allowed BRC range and fees. The check reserves no capacity and does not check your actual balance or the recipient wallet's capacity. During maintenance, new orders are paused; existing orders and accounts remain queryable.

The allowed range intersects fee and storage requirements, the merchant's single-order limits, and its remaining daily quota. Collections and payouts each count the gross BRC amount of accepted orders in the Sao Paulo calendar day. Unpaid and expired orders still count on their creation day; rolled-back order creation does not count. A merchant amount limit configured as 0 adds no restriction for that limit. New orders still require a positive amount and must satisfy fee and balance requirements.

Request

Request field Type Required Description
direction string No PAYIN checks collections; PAYOUT checks payouts. Omit to check both.
{}

To check payouts only, send:

{
  "direction": "PAYOUT"
}

Success response data

With {}, data includes both payin and payout. With direction, it contains only the requested object. Each object contains:

Response field Type Description
acceptingOrders boolean Whether new orders are currently accepted, based on maintenance, the merchant's direction switch, single-order range, remaining daily quota, and fee requirements.
minimumAmount string / null Minimum BRC order quantity (inclusive); null if there is no valid range.
maximumAmount string / null Maximum BRC order quantity (inclusive); null together with the minimum if there is no valid range.
feeRate string Decimal rate from 0 to 1, up to six decimal places. "0.01" means 1%.
feeFixed string Fixed fee per order, in BRC. Can be "0.00".
feeMin string Minimum fee per order, in BRC.

A disabled direction can still return a range. Check acceptingOrders; even when true, the platform checks eligibility again when you submit an order.

Full response example · HTTP 200

{
  "success": true,
  "code": "OK",
  "message": "",
  "requestId": "44444444-5555-4666-8777-999999999999",
  "timestamp": "2026-11-05T03:59:00Z",
  "data": {
    "payin": {
      "acceptingOrders": true,
      "minimumAmount": "1.00",
      "maximumAmount": "10000.00",
      "feeRate": "0.01",
      "feeFixed": "0.10",
      "feeMin": "0.00"
    },
    "payout": {
      "acceptingOrders": true,
      "minimumAmount": "1.00",
      "maximumAmount": "10000.00",
      "feeRate": "0.02",
      "feeFixed": "0.20",
      "feeMin": "0.00"
    }
  }
}

Full payout-only response · HTTP 200

{
  "success": true,
  "code": "OK",
  "message": "",
  "requestId": "44444444-5555-4666-8777-aaaaaaaaaaaa",
  "timestamp": "2026-11-05T03:59:10Z",
  "data": {
    "payout": {
      "acceptingOrders": true,
      "minimumAmount": "1.00",
      "maximumAmount": "10000.00",
      "feeRate": "0.02",
      "feeFixed": "0.20",
      "feeMin": "0.00"
    }
  }
}

4.5 Query merchant balance

POST /api/merchant/account/balance · SDK queryBalance(...)

One snapshot returns total, held and available BRC balances as decimal strings. Payout acceptance atomically reserves principal plus fees and rejects insufficient available funds before creating an order. Pending pay-ins and pending notifications do not hold funds.

Request

No business fields. Send an empty JSON object {}. Do not omit the body.

{}

Success response data

Response field Type Description
currency string Always BRC.
balance string Total booked BRC balance including held funds.
heldBalance string Generic occupied BRC funds, temporarily unavailable. Includes payout principal plus fees until settlement or release; not limited to payouts.
availableBalance string BRC available for new business: balance - heldBalance.
asOf string Balance read time in UTC.

Full response example · HTTP 200

{
  "success": true,
  "code": "OK",
  "message": "",
  "requestId": "55555555-6666-4777-8888-aaaaaaaaaaaa",
  "timestamp": "2026-11-05T04:04:00Z",
  "data": {
    "currency": "BRC",
    "balance": "29.60",
    "heldBalance": "5.30",
    "availableBalance": "24.30",
    "asOf": "2026-11-05T04:04:00Z"
  }
}

If the query fails: Show the error; do not display a zero balance as a fallback.

4.6 Query merchant transactions

POST /api/merchant/account/transactions · SDK queryTransactions(...)

List posted changes to the merchant balance and match them to orders using either order number. Pages contain up to 50 entries, sorted by createdAt descending and then id ascending. New entries can shift the pages; use id to recognize a repeated entry.

Request

Request field Type Required Description
page integer No Zero-based JSON integer. Defaults to 0; maximum 2147483647.
{
  "page": 0
}

Success response data

Response field Type Description
page integer Current page number.
hasMore boolean If true, request page + 1 for more entries.
rows array Entries on this page, or [] if there are none.

Fields in each rows[] entry

Response field Type Description
id string Ledger entry UUID; use it to recognize the same entry.
orderNo string Related BRPAY order number: RC or PY + 14 digits.
merchantOrderNo string Your reference for the related order.
direction string PAYIN or PAYOUT.
amount string Signed change in the booked BRC balance. A reversal has the opposite sign to the original entry; use the amount, not the order direction, for reconciliation.
feeAmount string Order fee in BRC, already reflected in amount.
balanceAfter string Merchant BRC balance after the entry was posted.
createdAt string Ledger entry timestamp in UTC.

Full response example · HTTP 200

{
  "success": true,
  "code": "OK",
  "message": "",
  "requestId": "66666666-7777-4888-8999-bbbbbbbbbbbb",
  "timestamp": "2026-11-05T04:04:30Z",
  "data": {
    "page": 0,
    "hasMore": false,
    "rows": [
      {
        "id": "66666666-7777-4888-8999-aaaaaaaaaaaa",
        "orderNo": "RC00000000128364",
        "merchantOrderNo": "RECHARGE-001",
        "direction": "PAYIN",
        "amount": "29.60",
        "feeAmount": "0.40",
        "balanceAfter": "29.60",
        "createdAt": "2026-11-05T04:02:00Z"
      }
    ]
  }
}

This is a live ledger view. Fetching multiple pages does not produce a point-in-time statement.

4.7 Look up wallet

POST /api/merchant/wallets/query · SDK queryWallet(...)

Check whether the customer's BRPAY wallet can receive a payout. No name, balance or other personal details are returned. Confirm the intended recipient in your own business flow.

Request

Request field Type Required Description
account string Yes Uppercase W + 8 uppercase letters or digits, for example W00012836.
{
  "account": "W00012836"
}

Success response data

Response field Type Description
exists boolean Whether the wallet exists. When false, this is the only field.
walletId string Present when the wallet exists. Pass it as recipientWalletId when creating a payout.
account string Present when the wallet exists. The matching wallet account (W + 8 uppercase letters or digits).
canReceive boolean Present when the wallet exists. true means it can currently receive funds; the payout still checks the account and wallet capacity.

Full response example · HTTP 200

{
  "success": true,
  "code": "OK",
  "message": "",
  "requestId": "77777777-8888-4999-8aaa-bbbbbbbbbbbb",
  "timestamp": "2026-11-05T04:04:45Z",
  "data": {
    "exists": true,
    "walletId": "W00012836",
    "account": "W00012836",
    "canReceive": true
  }
}

An unknown wallet still returns HTTP 200, with only exists: false inside data. Ask the customer to check the wallet number before submitting a payout.

{
  "success": true,
  "code": "OK",
  "message": "",
  "requestId": "88888888-9999-4aaa-8bbb-cccccccccccc",
  "timestamp": "2026-11-05T04:04:50Z",
  "data": {
    "exists": false
  }
}

5. Result callbacks

5.1 Receiving and verification

Order requests optionally accept notifyUrl for asynchronous result notifications and returnUrl for the synchronous merchant return page. Each URL is saved with the order, up to 512 ASCII characters; percent-encode non-ASCII content. Use a full HTTPS URL without user credentials, fragments or control characters. Only local verification allows http://127.0.0.1. Omitting notifyUrl creates no notification and leaves notificationStatus null; omitting returnUrl hides the merchant return action. Null and empty strings mean omitted; invalid nonempty URLs are rejected. Both directions accept both fields. PAYOUT has no cashier: its return URL is stored without navigation. A terminal PAYIN result page offers a Return to merchant button using the saved URL unchanged, without automatic redirects or appended result parameters. A browser return is not proof of payment; verify a signed notification or query the order server-side.

URLs belong to orders. Each PAYIN or PAYOUT uses its own request URLs. New requests do not alter existing orders or retries. The platform must still allow the notification HTTPS origin for outbound delivery.

BRPAY sends an HTTP POST with UTF-8 JSON. Return the HTTP status directly; redirects are not followed.

Header Description
X-BrazilPay-Event-Id Lowercase UUID equal to body eventId
X-BrazilPay-Timestamp Delivery time in UTC RFC3339 ending in Z, unlike the API request’s Unix seconds
X-BrazilPay-Signature 64-character lowercase HMAC-SHA256 hex

Sign the following byte sequence using the exact received UTF-8 body, without appending a newline:

canonicalBytes = utf8(timestamp + "\n" + eventId + "\n") + rawBodyBytes
signature = hmacSha256Hex(utf8(signingSecret), canonicalBytes)

Compare signatures in constant time. The SDK checks signature, matching header/body event IDs, order fields and a 300-second delivery timestamp window. occurredAt may be an older business time; do not substitute it for delivery time validation.

5.2 Event format

ORDER_SUCCEEDED is sent after a PAYIN is paid or a PAYOUT is credited. ORDER_FAILED is sent for an expired PAYIN or a failed PAYOUT. There is no result callback while payment is pending.

Callback field Type Description
eventId string Event UUID, matching X-BrazilPay-Event-Id; stable across delivery attempts.
eventType string ORDER_SUCCEEDED, ORDER_FAILED or ORDER_CORRECTED.
occurredAt string When the order reached its terminal state, equal to data.completedAt in UTC.
data object Order result, with the fields listed below.

Fields in data

Callback field Type Description
orderNo string BRPAY order number: RC for PAYIN or PY for PAYOUT, followed by 14 digits.
merchantOrderNo string Your order number from the original request.
direction string PAYIN or PAYOUT.
currency string Always BRC.
orderAmount string Original order quantity in BRC.
feeAmount string Fee fixed at order creation, in BRC. Can be "0.00".
merchantAmount string Positive BRC quantity: net PAYIN credit or total PAYOUT debit including the fee. An expired PAYIN is not credited.
actualAmount string / null BRC debited from the payer (PAYIN) or credited to the recipient (PAYOUT) on success; null on expiry.
status string SUCCESS for a paid PAYIN or credited PAYOUT; FAILED for an expired PAYIN or a confirmed failure; see failureCode.
failureCode string / null Failure reason; required for FAILED. Current expiry code: ORDER_EXPIRED. Null otherwise.
walletId string / null PAYOUT: designated recipient in every terminal state. PAYIN: actual payer when known, otherwise null. A wallet identifier alone does not confirm credit.
createdAt string Order creation time in UTC.
expiresAt string / null PAYIN payment deadline in UTC; null for PAYOUT.
completedAt string Terminal order time in UTC: completion on success, expiresAt on expiry.

These are three complete callback bodies. BRPAY sends them as HTTP requests with eventId, eventType, occurredAt and data at the top level.

PAYIN paid · ORDER_SUCCEEDED

{
  "eventId": "44444444-5555-4666-8777-888888888888",
  "eventType": "ORDER_SUCCEEDED",
  "occurredAt": "2026-11-05T04:02:00Z",
  "data": {
    "orderNo": "RC00000000128364",
    "merchantOrderNo": "RECHARGE-001",
    "direction": "PAYIN",
    "currency": "BRC",
    "orderAmount": "30.00",
    "feeAmount": "0.40",
    "merchantAmount": "29.60",
    "actualAmount": "30.00",
    "status": "SUCCESS",
    "walletId": "W00012836",
    "createdAt": "2026-11-05T04:00:00Z",
    "expiresAt": "2026-11-05T04:30:00Z",
    "completedAt": "2026-11-05T04:02:00Z",
    "failureCode": null
  }
}

HTTP headers for the successful PAYIN (offline signature check). Recompute the signature using the sample secret in §2.2 and the successful PAYIN JSON above, encoded as UTF-8 with LF line breaks and no trailing newline. For a time-window test, set the test clock to X-BrazilPay-Timestamp; use the current time in your real receiver.

POST /webhooks/br-pay HTTP/1.1
Host: merchant.example.com
Content-Type: application/json; charset=utf-8
X-BrazilPay-Event-Id: 44444444-5555-4666-8777-888888888888
X-BrazilPay-Timestamp: 2026-11-05T04:02:01Z
X-BrazilPay-Signature: be9644982cae18aead3d900226ca585e2d30e6ab62370147e6f049404ce858a0
Content-Length: 585

PAYOUT credited · ORDER_SUCCEEDED

{
  "eventId": "44444444-5555-4666-8777-bbbbbbbbbbbb",
  "eventType": "ORDER_SUCCEEDED",
  "occurredAt": "2026-11-05T04:05:00Z",
  "data": {
    "orderNo": "PY00000000128365",
    "merchantOrderNo": "WITHDRAW-001",
    "direction": "PAYOUT",
    "currency": "BRC",
    "orderAmount": "20.00",
    "feeAmount": "0.60",
    "merchantAmount": "20.60",
    "actualAmount": "20.00",
    "status": "SUCCESS",
    "walletId": "W00012836",
    "createdAt": "2026-11-05T04:05:00Z",
    "expiresAt": null,
    "completedAt": "2026-11-05T04:05:00Z"
  }
}

PAYIN expired without payment · ORDER_FAILED

{
  "eventId": "44444444-5555-4666-8777-aaaaaaaaaaaa",
  "eventType": "ORDER_FAILED",
  "occurredAt": "2026-11-05T04:30:00Z",
  "data": {
    "orderNo": "RC00000000128366",
    "merchantOrderNo": "RECHARGE-002",
    "direction": "PAYIN",
    "currency": "BRC",
    "orderAmount": "30.00",
    "feeAmount": "0.40",
    "merchantAmount": "29.60",
    "actualAmount": null,
    "status": "FAILED",
    "walletId": null,
    "createdAt": "2026-11-05T04:00:00Z",
    "expiresAt": "2026-11-05T04:30:00Z",
    "completedAt": "2026-11-05T04:30:00Z",
    "failureCode": "ORDER_EXPIRED"
  }
}

5.3 Acknowledgment and repeated delivery

  1. Verify the signature first. Keep the raw body and verify it using the three callback headers. If verification fails, do not update an order or return 2xx.
  2. Find your order. Look up data.merchantOrderNo in your system. Check the direction, BRC quantity, saved BRPAY order number and, for PAYOUT, the recipient wallet. If the callback arrives before the order response and you do not yet have the BRPAY order number, query by your original merchant order number first.
  3. Apply the result. On ORDER_SUCCEEDED, credit the game account or confirm the withdrawal only if your order has not been processed. On ORDER_FAILED, do not credit a failed PAYIN or mark a failed PAYOUT as paid. Handle ORDER_CORRECTED using its revision.
  4. Acknowledge after saving. After your order and related changes are saved, return 204 No Content. Any 2xx works; no response body is needed. If the same event is delivered again and you confirm it is already handled, return 2xx again.

Delivery runs after the payment transaction commits. Repeated deliveries retain eventId and the original business body, with new delivery timestamps and signatures. Delivery timeout, failure or EXHAUSTED never rolls back successful funds. Current adjustable engineering defaults: at most 10 automatic attempts, 5-second HTTP timeout, exponential delay starting at 5 seconds capped at 300 seconds. Persist essential work within 5 seconds and perform lengthy fulfillment in your own background system. When the budget is exhausted, query the order and contact the platform for manual redelivery.

When outbound origins are unconfigured, the platform preserves the pending notification with DELIVERY_DISABLED without consuming HTTP attempts. Do not create another funds order because of this condition.

6. Errors and unconfirmed outcomes

HTTP code Action
400 MERCHANT_REQUEST_INVALID Invalid fields, JSON or UUID; correct input
401 MERCHANT_AUTH_REQUIRED / INVALID_SIGNATURE / SIGNATURE_EXPIRED Check merchant ID, secret, raw bytes, path and clock
403 MERCHANT_DIRECTION_DISABLED / ORIGIN_REJECTED Query availability if closed by merchant; for rejected Origin, call from your merchant server
403 ACCOUNT_DISABLED Recipient account is disabled. Stop payouts to it and contact the platform; recheck wallet canReceive rather than changing the merchant order number
404 ORDER_NOT_FOUND / WALLET_NOT_FOUND Check order or wallet; wallet lookup normally returns exists=false when missing
405 METHOD_NOT_ALLOWED Use POST; response includes Allow: POST
409 ORDER_EXISTS Query and compare the existing order; conflict is not success
409 MERCHANT_INSUFFICIENT_BALANCE / WALLET_LIMIT_EXCEEDED Insufficient merchant balance or user wallet capacity
409 CONFIG_CHANGED / MERCHANT_DAILY_LIMIT Configuration changed or daily amount limit reached; recheck availability
413 BODY_TOO_LARGE Body exceeds 8192 bytes
415 JSON_REQUIRED Send application/json
422 INVALID_AMOUNT / INVALID_FEE / MERCHANT_AMOUNT_OUT_OF_RANGE Check quantity strings, fees and current allowed range
503 MAINTENANCE_CONFIG_UNAVAILABLE Maintenance configuration cannot be verified, so new orders are rejected. Contact the platform; treat funds receipts as unconfirmed and query the original order first
503 CLIENT_MAINTENANCE Maintenance closes new orders; existing orders/accounts remain queryable
503 MERCHANT_SCHEMA_REQUIRED / CLIENT_MAINTENANCE / SERVICE_UNAVAILABLE Service/configuration unavailable; contact platform and query unconfirmed writes first

Codes are case-sensitive. Treat HTTP 5xx, disconnects, timeouts, and invalid or mismatched receipts as unconfirmed. Query the original merchant order number before taking any further action.

Example: PAYOUT WITHDRAW-001 times out.

  1. Keep the original request, merchant order number WITHDRAW-001 and request trace ID. Do not change your balance or make a payout with a new order number.
  2. Query the original order with {"merchantOrderNo":"WITHDRAW-001"}. If it is SUCCESS, verify the direction, BRC quantity and recipient wallet before marking it credited. If this was a PAYIN, handle its PENDING, SUCCESS or FAILED status.
  3. If the query also times out or returns 5xx, wait and query again. Resubmit only after an explicit 404 / ORDER_NOT_FOUND, using the original merchant order number and the original business fields. The first request may still be running. If you get 409 / ORDER_EXISTS, query that same order.

The Java and PHP SDKs use MerchantApiException for explicit rejections and UnknownOutcomeException for unconfirmed outcomes, including HTTP 5xx. Default total request deadline is 30 seconds including connection, headers and body, with a 256 KiB response limit. Configure deadlines for your actual environment. SDKs do not follow redirects automatically.

7. SDK usage

7.1 Java

Requires Java 17+. Use merchant-sdk.jar with its supplied standalone POM, or build sdk/java. Dependency: tools.jackson.core:jackson-databind:3.1.4. The following demonstrates individual calls, not an automatic sequence. Quantities and recipients require your own business validation:

import java.time.Duration;
import tech.brazilpay.merchant.sdk.MerchantClient;
import tech.brazilpay.merchant.sdk.MerchantWebhookVerifier;

// Load settings from your server-side configuration / secret store.
var client = new MerchantClient(baseUrl, merchantId, signingSecret, Duration.ofSeconds(30));
var balance = client.queryBalance();
String availableBalance = balance.body().path("data").path("availableBalance").asText();
var availability = client.queryAvailability("PAYOUT");
var wallet = client.queryWallet("W00012836");
// Check exists / canReceive and confirm the actual recipient before payout.
var payin = client.payin(new MerchantClient.PayinRequest("RECHARGE-001", "30.00", "Game recharge", notifyUrl, returnUrl));
var payout = client.payout(new MerchantClient.PayoutRequest("WITHDRAW-001", "20.00", "Game withdrawal", recipientWalletId, notifyUrl, returnUrl));
var order = client.queryOrder("RECHARGE-001");
var orderByPlatformNo = client.querySystemOrder("RC00000000128364");
var transactions = client.queryTransactions(0);

// Inside your HTTP callback receiver; rawBodyBytes must be the untouched body.
var event = new MerchantWebhookVerifier(signingSecret)
    .verifyAndParse(timestampHeader, eventIdHeader, signatureHeader, rawBodyBytes);
// Verify your business order, persist under its state conditions, then return 2xx.

7.2 PHP

Requires PHP 8.1+ with cURL and JSON. Use Composer PSR-4 namespace BrazilPay\Merchant, or require src/*.php. The CLI demo.php queries balance by default without creating orders.

use BrazilPay\Merchant\MerchantClient;
use BrazilPay\Merchant\MerchantWebhookVerifier;

// Require Composer autoload.php, or the supplied src/*.php classes first.
$client = new MerchantClient($baseUrl, $merchantId, $signingSecret, 30);
$balance = $client->queryBalance();
$availableBalance = $balance['data']['availableBalance'];
$availability = $client->queryAvailability('PAYOUT');
$wallet = $client->queryWallet('W00012836');
// Check exists / canReceive and confirm the actual recipient before payout.
$payin = $client->payin(['merchantOrderNo'=>'RECHARGE-001', 'orderAmount'=>'30.00', 'description'=>'Game recharge', 'notifyUrl'=>$notifyUrl, 'returnUrl'=>$returnUrl]);
$payout = $client->payout(['merchantOrderNo'=>'WITHDRAW-001', 'orderAmount'=>'20.00', 'description'=>'Game withdrawal', 'recipientWalletId'=>$recipientWalletId, 'notifyUrl'=>$notifyUrl, 'returnUrl'=>$returnUrl]);
$order = $client->queryOrder('RECHARGE-001');
$orderByPlatformNo = $client->querySystemOrder('RC00000000128364');
$transactions = $client->queryTransactions(0);

// Inside your HTTP callback receiver; reject failures before business changes.
$rawBody = file_get_contents('php://input');
$event = (new MerchantWebhookVerifier($signingSecret))->verifyAndParse(
    $_SERVER['HTTP_X_BRAZILPAY_TIMESTAMP'] ?? '',
    $_SERVER['HTTP_X_BRAZILPAY_EVENT_ID'] ?? '',
    $_SERVER['HTTP_X_BRAZILPAY_SIGNATURE'] ?? '',
    $rawBody
);
// Verify your business order, persist under its state conditions, then return 2xx.

8. App payment and web guidance

  1. Create PAYIN on the merchant server. The client opens cashierUrl, or opens appUrl with Android ACTION_VIEW.
  2. A valid Web session opens confirmation for the original order directly. Other users can pay on the Web, open the native app, or scan the checkout QR in the app. A confirmed installation makes opening the app the primary action. Links carry a random order credential and do not authorize a debit.
  3. Login/registration, profile and payment password setup return to the original order. If the checkout page is closed, reopen it from the merchant order.
  4. Honor server expiresAt; installation does not extend it. Complete the merchant business only after SUCCESS from a signed callback or server-side query.

Opening the app does not prove payment. Production HTTPS App Links association, release signing and real-device payments require separate acceptance. The configured payment origin must actually serve /pay/*, not just a website without the payment entry. The SDK accepts only the current /pay/ path.

9. Local SDK DEMO

Open the local DEMO. Its /api/* paths belong to the test workspace. Use the baseUrl supplied by BRPAY in your integration. To test the new transaction API with the DEMO, first check that both use the same version. Download OpenAPI from the top of this page.

Before testing callbacks, ask BRPAY to verify the callback URL and notification delivery for your test merchant. Passing a read-only query does not verify callback delivery.

10. Integration acceptance

Administrative order corrections

After verifying the actual funds, an administrator may settle a pending order or correct a terminal result. The original principal and fee remain unchanged; compensating ledger entries preserve the complete history.

ORDER_CORRECTED carries a new event ID, correctionId, previousStatus (PENDING / SUCCESS / FAILED), and a positive increasing data.orderRevision. Its data is the corrected terminal snapshot. Failed corrections use MANUAL_CORRECTION; a failed pay-in can retain its original payer wallet ID. Queries expose orderRevision; original settlement events have revision 0.

Persist the last processed terminal revision in the merchant's own order transaction. Before any terminal result has been processed, accept the first normal success or failure event at revision 0. After that, only a newer revision can replace the terminal result. Revision 0 on a PENDING query does not mark a terminal result as processed and must not suppress the following normal terminal event. Late older events and repeated delivery must not repeat fulfillment. A correction is not a second deposit. Reconcile any previous game credit or fulfillment explicitly. Each identified event retains its immutable body. Java and PHP verifiers accept correction events, and the demo displays the snapshot; its in-memory event list is not a durable merchant ledger.