Payin Integration Guide
This guide explains how merchants create a Payin acquiring order, redirect the user to pay, handle order webhooks, and query the final result.
Payin is a three-step flow:
- Create the order: call
POST /payment/acquiring/orders. - Let the user pay: redirect the user to
checkout_url, where the user selects a currency and network. - Confirm the result: receive
acquiring.order.updatedwebhooks and query order detail for fallback confirmation and reconciliation.
One-Page Summary
Minimum API set:
POST /payment/acquiring/ordersGET /payment/acquiring/orders/{order_id}
Supplementary APIs:
GET /payment/acquiring/orders/{order_id}/payments: list payment records under the orderPOST /payment/acquiring/orders/{order_id}/close: close an unfinished order early
Minimum webhook:
acquiring.order.updated: order status changed
To receive webhooks, pass a valid notify_webhook_id when creating the order. The webhook must be active and subscribed to acquiring.order.updated, acquiring.payment.updated, or acquiring.*.
Before Integration
Confirm these items before creating live orders:
| Item | Why it matters |
|---|---|
| API key permissions | Creating orders requires acquiring create permission. Querying orders requires acquiring read permission. Closing orders requires acquiring close permission. |
| Payment options | Confirm the pay tokens enabled for the account, such as TRON_USDT or BSC_USDT. |
| Merchant order number | Send out_trade_no for every order and keep it unique in the merchant system. |
| Webhook configuration | Create and enable the webhook first, then pass its webhook_id as notify_webhook_id for every order. |
| Merchant storage | Store at least out_trade_no, order_id, status, checkout_url, and expire_time. |
Confirm Payment Methods
Before the first integration, or before adding a new payment route, the merchant needs to know which currencies and networks users can pay with.
A pay token ID represents a network and a currency. The usual format is:
<NETWORK>_<CURRENCY>
Examples:
| Pay token ID | Meaning | User pays with |
|---|---|---|
TRON_USDT | USDT on TRON | USDT transfer on TRON |
ETH_USDT | USDT on Ethereum | USDT transfer on Ethereum |
BSC_USDT | USDT on BNB Smart Chain | USDT transfer on BSC |
ETH_USDC | USDC on Ethereum | USDC transfer on Ethereum |
BASE_USDC | USDC on Base | USDC transfer on Base |
Confirm these values:
| Value needed | Example | Used in request |
|---|---|---|
| Pricing currency | USD | order_currency |
| Pay token ID | TRON_USDT, BSC_USDT | allowed_pay_currencies; when omitted, system default tokens are used |
| Webhook ID | webhook_xxx | notify_webhook_id |
These values are usually confirmed through Beyounger or the merchant dashboard. When creating an order, the merchant can send token IDs in allowed_pay_currencies to restrict payment methods. When omitted, the system uses the default tokens enabled for the account.
Common token IDs:
| Currency | Available token IDs |
|---|---|
| USDT | TRON_USDT, ETH_USDT, BSC_USDT, MATIC_USDT, ARBITRUM_USDT, SOL_USDT |
| USDC | ETH_USDC, BASE_USDC, BSC_USDC, MATIC_USDC, ARBITRUM_USDC, SOL_USDC |
Recommendations:
- Only include token IDs that the merchant wants to allow for the user.
- To allow only TRON USDT, send
["TRON_USDT"]. - To allow USDT on multiple networks, send
["TRON_USDT", "ETH_USDT", "BSC_USDT"]. - Do not send only
USDTorUSDC; send the full token ID. - Do not include token IDs that are not enabled or not supported for the merchant.
Core Sequence
Step 1: Create The Order
Endpoint
POST /payment/acquiring/orders
Use this endpoint to create a Payin order. After the order is created, Beyounger returns checkout_url; the merchant frontend should redirect the user to that URL to complete payment.
Headers
Authorization: Bearer <api_key_jwt>
Content-Type: application/json
Accept: application/json
Request Fields
| Field | Required | Description |
|---|---|---|
out_trade_no | Yes | Merchant order number. Must be unique for each order. |
order_currency | Yes | Pricing currency. Currently supports USD. |
order_amount | Yes | Order amount. Must be greater than 0. |
allowed_pay_currencies | Optional | Token IDs the user is allowed to pay with, for example ["TRON_USDT", "BSC_USDT"]. When omitted, system default tokens are used. |
expire_seconds | Yes | Order validity window, from 60 to 10800 seconds. |
underpay_tolerance | Yes | Underpayment tolerance. Must be greater than or equal to 0. |
notify_webhook_id | Yes | Webhook ID used for order status notifications. |
redirect_url | Optional | Where the user returns after checkout. Must be an HTTPS absolute URL or same-origin relative path. |
logo_url | Optional | Merchant logo shown on checkout. Must be HTTPS. |
remark | Optional | Merchant note. |
Create Order Example
{
"out_trade_no": "payin_202606190001",
"order_currency": "USD",
"order_amount": "20.00",
"allowed_pay_currencies": ["TRON_USDT", "BSC_USDT"],
"expire_seconds": 1800,
"underpay_tolerance": "0",
"notify_webhook_id": "webhook_xxx",
"redirect_url": "https://merchant.example.com/payment/result",
"remark": "Order payin_202606190001"
}
Successful Response
{
"code": 0,
"message": "OK",
"data": {
"order": {
"order_id": "acq_order_xxx",
"platform_order_no": "P202606190001",
"out_trade_no": "payin_202606190001",
"status": "pending",
"checkout_type": "default",
"order_currency": "USD",
"order_amount": "20.00",
"allowed_pay_currencies": ["TRON_USDT", "BSC_USDT"],
"pay_amount": "20.00",
"paid_amount_total": "0",
"remaining_amount": "20.00",
"underpay_tolerance": "0",
"checkout_url": "https://api.example.com/payment/checkout/acq_order_xxx",
"notify_webhook_id": "webhook_xxx",
"expire_time": 1781789497,
"created_at": 1781787697,
"updated_at": 1781787697
}
}
}
A successful create response means the Payin order exists. It does not mean the user has paid.
Store these fields:
out_trade_noorder_idstatuscheckout_urlallowed_pay_currencies, if provided in the requestpay_amountexpire_time
Step 2: User Payment And Webhook
The merchant frontend redirects the user to checkout_url or displays it inside the merchant experience. After the user pays, Beyounger updates the order based on provider or on-chain results.
If the order was created with a valid notify_webhook_id, Beyounger sends:
acquiring.order.updated
The merchant webhook endpoint should:
- Verify the webhook signature.
- Process repeated events idempotently by
event_idorbusiness_id. - Read
data.order_id,data.out_trade_no, anddata.status. - Query
GET /payment/acquiring/orders/{order_id}when confirmation is needed. - Update the merchant order state.
Webhook Payload Example
{
"event_id": "acqevt_acq_order_xxx",
"event_type": "acquiring.order.updated",
"business_type": "acquiring",
"business_id": "acq_order_xxx",
"occurred_at": 1781789497,
"version": "v1",
"data": {
"provider_event": "transaction_confirmed",
"anomaly_tag": "",
"order_id": "acq_order_xxx",
"platform_order_no": "P202606190001",
"out_trade_no": "payin_202606190001",
"previous_status": "pending",
"status": "completed",
"status_changed": true,
"amount_changed": true,
"order_currency": "USD",
"pay_amount": "20.00",
"paid_amount_total": "20.00",
"remaining_amount": "0",
"overpaid_amount": "0",
"underpay_tolerance": "0",
"expire_time": 1781789497,
"updated_at": 1781789497
}
}
Important fields:
| Field | Description |
|---|---|
event_id | Webhook event ID. |
event_type | acquiring.order.updated. |
business_type | Always acquiring. |
business_id | Same as order_id. |
data.out_trade_no | Merchant order number. |
data.status | Latest order status. |
data.paid_amount_total | Total paid amount. |
data.remaining_amount | Remaining amount. |
data.anomaly_tag | Anomaly marker, such as late payment after expiry. |
Crypto Payin Status Notes
Crypto Payin is not a simple success/failure model. The order status is aggregated from the payment records under the order. The key inputs are confirmed paid amount, expected order amount, underpayment tolerance, and whether the order has expired.
When handling webhooks, use these rules:
data.status in webhook | Final? | Meaning | Merchant action |
|---|---|---|---|
partial_paid | No | Some amount has been confirmed, but confirmed amount has not reached pay_amount - underpay_tolerance. | Do not mark the order paid. Wait for more payment or ask the user to top up according to merchant policy. |
completed | Yes | Confirmed amount plus underpayment tolerance satisfies the expected amount. | Mark the merchant order as paid and continue fulfillment. |
underpaid | Yes | The order expired with some confirmed amount, but it is still short beyond the tolerance. | Mark as exception and handle manually, request top-up, or refund according to merchant policy. |
expired | Yes | The order expired with no confirmed paid amount. | Mark the merchant order expired and stop using this order for payment. |
Also note:
pending,confirming, andprocessingare more common in query results or payment records. They usually mean waiting for user payment, broadcast, or confirmations.paid_amount_totalis confirmed paid amount, not merely broadcast or unconfirmed amount.remaining_amountis calculated from confirmed paid amount.overpaid_amountgreater than 0 means the user overpaid and the merchant should handle the extra amount by business policy.anomaly_tag=expired_paidmeans payment was detected after the order expired. Do not automatically treat it as a normal success; route it to exception handling.- One order can have multiple payment records. Use the aggregated
data.statusas the order-level business result.
Step 3: Query Order Status
Endpoint
GET /payment/acquiring/orders/{order_id}
Use this endpoint:
- After create, to read the initial state.
- After receiving a webhook, to confirm the latest state.
- When a webhook times out or is missing.
- During finance or operations reconciliation.
State Guide
status | Meaning | Merchant action |
|---|---|---|
pending | Waiting for user payment. | Keep the merchant order unpaid. |
processing | Payment detected and processing. | Wait for the next status. |
confirming | Waiting for confirmations. | Wait for completion. |
partial_paid | Partially paid. | Continue waiting or handle by merchant policy. |
completed | Payment completed. | Mark the merchant order as paid. |
underpaid | Underpaid beyond tolerance. | Mark as exception and handle manually or refund. |
expired | Order expired. | Mark as expired; if money arrives later, inspect anomaly_tag. |
Final success should be based on status=completed. Do not mark the merchant order paid based only on frontend redirects or the create-order response.
Query Payment Records
Endpoint
GET /payment/acquiring/orders/{order_id}/payments
This API is for investigation and reconciliation. It is not required for the minimum integration.
Use it to:
- Inspect the user's selected currency and network.
- Inspect the receiving address.
- Read
tx_hash. - Investigate underpayment, overpayment, currency mismatch, or network mismatch.
Payment statuses:
payment.status | Meaning |
|---|---|
pending | Waiting for payment. |
processing | Payment received and processing. |
confirming | Waiting for confirmations. |
completed | This payment completed. |
failed | Payment failed. |
late | Paid after expiry. |
expired | Payment record expired. |
Close An Order
Endpoint
POST /payment/acquiring/orders/{order_id}/close
Use this when the merchant order was cancelled, replaced, or should no longer accept payment.
{
"reason": "merchant_order_cancelled"
}
Use close only for unfinished orders. Do not use it to handle orders that have already completed.
Common Questions
Does a successful create response mean payment success?
No. It only means the Payin order was created. Final success is confirmed by webhook or query result.
Does Payin support per-order notify_url?
The current order creation API uses notify_webhook_id, not per-order notify_url. Create a webhook first, then pass its webhook_id in the order request.
How should allowed_pay_currencies be set?
Send allowed_pay_currencies when the merchant wants to restrict which tokens or networks the user can pay with. If omitted, the system uses the default tokens enabled for the account.
When provided, use the token IDs confirmed through Beyounger or the merchant dashboard. A token ID is network plus currency, such as TRON_USDT for USDT on TRON.
Example:
{
"allowed_pay_currencies": ["TRON_USDT", "BSC_USDT"]
}
Should the merchant rely on webhook or query?
Use both. Webhook gives real-time state changes; query is the fallback for timeout, missing notification, and reconciliation.