Foreign Currency Withdrawals (Pay-Out)
This module allows you to create withdrawals (pay-out) in local currency using source funds in foreign currency (for example, USDC → MXN).
As a Merchant, your integration communicates exclusively with Pago46. Pago46 internally manages the quote, monitors the receipt of funds, and enables the cash withdrawal.
See the available pairs and countries for foreign currency, and sign every request following the Authentication guide.
All routes described below are relative to the API base URL:
/api/v1
General flow
- You create a quote for an asset pair (
POST /merchants/quotes/). - Your user accepts the quote in your application.
- You create a foreign withdrawal order associated with that quote.
- Your user sends the funds to the address indicated by Pago46.
- If the funding arrives within the valid window, the order advances to
READYand can continue the normal withdrawal flow.
Prerequisites
- Merchant credentials (
Merchant-KeyandMerchant-Secret). - HMAC signature on every request (
Message-Date,Message-Hash). - Public
notify_urlvia HTTPS for status changes. - At least one of
consumer_emailorconsumer_phone_numberwhen creating the order.
Check the Authentication section for the signature.
1) Create a quote
Request a quote for the desired asset pair.
Endpoint: POST /merchants/quotes/
Request fields
| Field | Type | Required | Description |
|---|---|---|---|
blockchain | String | Yes | Funding network (SOLANA, STELLAR, MONAD). |
send | Decimal | Yes | Amount the user will send. |
send_currency | String | Yes | Source currency (e.g., USDC). |
target_country | String | Yes | Destination country (MX, CL, etc.). |
target_currency | String | Yes | Target local currency (e.g., MXN). |
Request example
curl -X POST "https://api.dev.pago46.io/api/v1/merchants/quotes/" \
-H "Merchant-Key: <YOUR_MERCHANT_KEY>" \
-H "Message-Date: <TIMESTAMP>" \
-H "Message-Hash: <HMAC_SIGNATURE>" \
-H "Content-Type: application/json" \
-d '{
"blockchain": "SOLANA",
"send": "150.00",
"send_currency": "USDC",
"target_country": "MX",
"target_currency": "MXN"
}'
Quote response (201 Created)
{
"id": "01952d8f-8da1-7f4b-bb36-cfba8a3be829",
"blockchain": "SOLANA",
"send": "150.00",
"send_currency": "USDC",
"target_country": "MX",
"target_currency": "MXN",
"fee": "2.00",
"fee_currency": "USDC",
"order_price": "2895.00",
"order_price_currency": "MXN",
"rate": "19.56",
"expires": "2030-01-01T11:05:00Z"
}
Save the quote id. You will use it when creating the foreign order.
Creating a quote can fail with 400 if:
- your account is not configured for the destination country or currency
(
No matching configuration found for merchant, country, and currency), - the pair is not valid for the blockchain
(
<send_currency>:<target_currency> is not a valid pair for <blockchain> blockchain), - the amount to send is too low (
The amount to send is too low.).
424)If we can't secure a fair exchange rate at that moment, the quote returns 424
with A dependent operation failed.. This is a safeguard, not a bug in your
integration: we only issue a quote when the rate meets our guarantees, so your
user is never left with an unfavorable rate.
It's a temporary condition and can affect one blockchain in particular while
liquidity rebalances. If you hit it, retry in a few minutes; if one network
keeps returning 424, offer your user another supported blockchain (SOLANA,
STELLAR, MONAD) as an alternative path.
2) Create a foreign withdrawal order
When your user accepts the quote, create the foreign pay-out order using the
quote.
Endpoint: POST /merchants/orders/pay-out/
Request fields
| Field | Type | Required | Description |
|---|---|---|---|
order_type | String | Yes | Must be ForeignCurrencyOrder. |
quote | UUID | Yes | ID of the previously created quote. |
description | String | Yes | Transaction description. |
merchant_order_id | String | Yes | Unique ID from your system (unique per merchant). |
notify_url | URL | Yes | URL for status notifications. |
return_url | URL | Yes | Return URL. |
expiry | DateTime | Yes | Order expiration (ISO 8601). |
consumer_email | String | Conditional | Required if you don't send a phone number. |
consumer_phone_number | String | Conditional | Required if you don't send an email. |
wallet | String | Yes | Wallet that will sign the on-chain transaction. |
Request example
curl -X POST "https://api.dev.pago46.io/api/v1/merchants/orders/pay-out/" \
-H "Merchant-Key: <YOUR_MERCHANT_KEY>" \
-H "Message-Date: <TIMESTAMP>" \
-H "Message-Hash: <HMAC_SIGNATURE>" \
-H "Content-Type: application/json" \
-d '{
"quote": "01952d8f-8da1-7f4b-bb36-cfba8a3be829",
"description": "Cash withdrawal from USDC balance",
"merchant_order_id": "FX-PO-2026-0001",
"notify_url": "https://your-merchant.com/webhooks/pago46",
"return_url": "https://your-merchant.com/withdrawal/back",
"consumer_email": "user@example.com",
"expiry": "2030-01-03T12:05:00Z",
"wallet": "7EcDhSYGxXyscszYEp35KHN8vvw3svAuLKTzXwCFLtV",
"order_type": "ForeignCurrencyOrder"
}'
Do not assume a successful (201 Created) POST to /pay-out/ means the withdrawal is actually available!
- The order has been created, but it DOES NOT INCLUDE the blockchain transaction to sign.
- Pago46 will build the transaction asynchronously.
- When it's ready, you will receive a webhook (
CREATEDstatus) that includes thetransactionfield in the order. - You may receive multiple webhooks with the same
status(CREATED) but different content; treat the one containingtransactionas the "transaction ready" event. - Do not attempt to sign or show blockchain data to the user until you receive this webhook!
Immediate POST response (201 Created, without a transaction)
{
"id": "01952d91-a0ff-7f57-8f4e-68d95be01122",
"order_type": "ForeignCurrencyOrder",
"country": "MX",
"price": "2895.00",
"price_currency": "MXN",
"description": "Cash withdrawal from USDC balance",
"merchant_order_id": "FX-PO-2026-0001",
"status": "CREATED",
"notify_url": "https://your-merchant.com/webhooks/pago46",
"redirect_url": "https://checkout.dev.pago46.io/01952d91-a0ff-7f57-8f4e-68d95be01122",
"return_url": "https://your-merchant.com/withdrawal/back",
"consumer_email": "user@example.com",
"consumer_phone_number": "",
"expiry": "2030-01-03T12:05:00Z",
"paid": null,
"transaction": "",
"quote": "01952d8f-8da1-7f4b-bb36-cfba8a3be829",
"wallet": "7EcDhSYGxXyscszYEp35KHN8vvw3svAuLKTzXwCFLtV"
}
Webhook payload example (with a transaction)
{
"id": "01952d91-a0ff-7f57-8f4e-68d95be01122",
"order_type": "ForeignCurrencyOrder",
"country": "MX",
"price": "2895.00",
"price_currency": "MXN",
"description": "Cash withdrawal from USDC balance",
"merchant_order_id": "FX-PO-2026-0001",
"status": "CREATED",
"notify_url": "https://your-merchant.com/webhooks/pago46",
"redirect_url": "https://checkout.dev.pago46.io/01952d91-a0ff-7f57-8f4e-68d95be01122",
"return_url": "https://your-merchant.com/withdrawal/back",
"consumer_email": "user@example.com",
"consumer_phone_number": "",
"expiry": "2030-01-03T12:05:00Z",
"paid": null,
"quote": "01952d8f-8da1-7f4b-bb36-cfba8a3be829",
"transaction": "AgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADyTBSt8EsV++3pX0uVJVEfDjDp8mnxoqBQocHmFw2xTf62jiq5VGtOhOY4yZH7evGRh3UonSpGQKHYTU0EVXkCgAIBBxJXN/S1MQpq3laWmAefYKQbtIFHc3dbFnEnnIO8+VCswgaIfmL5OVR6yf1DpJxY0nPEwFUxZnKSpVis0mLyYvB+CadUk1OGINWUunRSHPvVZsHS9SxcAzkMV3l0YrJa87VfpoD7lSKyS7KXhZdFjn4tDD9PM7ZkSXL+bJrJU6WMj2H0hZpDLnPhzU1d1RhBedKdDIuLnQj09NPzfpJAUTubj9Njqmepn7FPWB/pRxyBJLcCwVALhYefbQnfHrKVYlqZDYlyLNK33Q83kGZK7y8fZH3WFOPQax4GGUN64gOvfJ3zTDIAyZPrGSRcP4/Qj+sgg0V135Yr0S5moofrpJa7uYWuPLoIHj1rtPdLNX3+MqAowL1C3mfnKmBZ/gHZOU2/2TY6lXGwBdMPZ/X4ph/o4AGrpI1iulm/FEsovQG37vM5zLFxniH7ltpr8uBf04yhJRTUvdC/74e97kFxMjiNAwZGb+UhFzL/7K26csOb57yM5bvF9xJrLEObOkAAAAAEedVb8jHAbu50xW7OaBUH/bGy3qP0jlECsc2iVrwTjwVKU1qZKSEGTSTocWDaOHx8NbXdvJK7geQfqEBBBUSNBt324ddloZPZy+FGzut5rBy0he1fWzeROoz1hX7/AKkG3fbh7nWP3hhCXbzkbM3athr8TYO5DSf+vfko2KGL/LQ/+if11/ZKdMCbHylYed5LCas238ndUUsyGqezjOXo4vh36D2uxoiPQ6u1rnsxG7eMNJYIl/zlp7gsFuIP8ZcyjRWyhzKZjkXq5kHeRVDaKaLEEPfSWQM7V8ohwWOR/wYNABhBWnljbytLbWRnbTNZZFN4Q1Z5SnFBPT0OBAYYCQAKDEBCDwAAAAAABgsABQLAXBUACwAJAwQXAQAAAAAADB0OEQEJCAcEGBcMDxAMFg8ODRETFxgHEggUAgMKFSbBIJszQdacgQABAAAALwAAZAABQEIPAAAAAADskeQAAAAAABQAAA8EBBcFAQoM7vXjAAAAAAAGAWdhe29wATESFFYXVPJ+3KgjZOfKxNroQxcYyDV2rOPsBJVakZgDllyS",
"wallet": "7EcDhSYGxXyscszYEp35KHN8vvw3svAuLKTzXwCFLtV"
}
3) Sign and broadcast the on-chain transaction
When the webhook with the transaction field arrives, that value is the
unsigned transaction that Pago46 already built. The format depends on
the quote's network (base64, XDR, JSON, etc.) — see the per-network detail
below. Your user must sign it with the wallet you provided when creating
the order and broadcast it to the network to deposit the funds to the
Pago46 address.
The procedure is each network's standard one; you don't need any Pago46-specific logic:
- Decode and parse the
transactionfield according to the network's format (see detail below). - Deserialize it with the corresponding blockchain SDK.
- Sign it with the user's
wallet. - Submit the signed transaction to the network and wait for confirmation.
Once the transaction is confirmed on-chain and Pago46 validates the receipt of
funds, the order advances to READY and you receive a webhook. This state
confirms that the on-chain funds reached Pago46 within the expected window and
that the order is ready to be disbursed in cash to the end user.
By the quote's network:
- Solana — the transaction is a
VersionedTransaction. Deserialize it from base64, sign it with the wallet keypair and send it through a Solana RPC. See @solana/web3.js and the Solana docs. - Stellar — the transaction is an XDR
TransactionEnvelope. Decode it, sign it with the account key and submit it to Horizon. See the Stellar JS SDK and the Stellar docs. - Monad — it is EVM-compatible. The
transactionfield arrives as a JSON object with the transaction parameters (not base64). Pass it to your usual library (ethers.js or viem) to sign and broadcast it like any Ethereum transaction. See the Monad docs.
Sandbox runs on the testnet of all three blockchains (Solana, Stellar and Monad); Production runs on mainnet. Point the wallet and the RPC to the correct network for each environment. See Environments for each one's base URLs.
In Sandbox we use a non-mintable test token for the source assets (for
example, USDC). To test on testnet you need us to fund you with that token:
request it from your account executive or email
contacto@pago46.com.
4) Retrieve an order
You can retrieve the withdrawal order whenever you need to verify its status.
Endpoint: GET /merchants/orders/pay-out/{id}/
curl -X GET "https://api.dev.pago46.io/api/v1/merchants/orders/pay-out/01952d91-a0ff-7f57-8f4e-68d95be01122/" \
-H "Merchant-Key: <YOUR_MERCHANT_KEY>" \
-H "Message-Date: <TIMESTAMP>" \
-H "Message-Hash: <HMAC_SIGNATURE>"
Order states (foreign pay-out)
As a merchant, you receive webhooks when the order reaches a final state. In addition, you receive a CREATED update containing the transaction you must sign and a READY webhook when we confirm the on-chain funds. The intermediate cash-handout step is handled internally.
| Status | Notified via webhook? | What does it mean for the merchant? |
|---|---|---|
CREATED | Response and update | The initial response registers the order without transaction; a later update, still in CREATED, includes the information you must sign |
READY | Yes | We confirmed receipt of the funds on-chain in the Pago46 wallet; the order is ready to be disbursed in cash |
PAYMENT_STARTED | No (internal) | Intermediate step: a provider is handing the cash to the beneficiary |
COMPLETED | Yes — final state | The withdrawal completed; the beneficiary received the cash |
CANCELLED | Yes — final state | The order was cancelled by the Pago46 team (neither the merchant nor the user can cancel) and will not be processed |
EXPIRED | Yes — final state | The order expired and will not be processed |
State transition diagram
Webhooks (notifications)
Throughout this flow you'll receive several webhook notifications at the notify_url you provided when creating the order: the CREATED update with the transaction to sign, READY when the on-chain funds are confirmed, and the final state (COMPLETED, CANCELLED, or EXPIRED). See the table in the previous section for the detail on each.
Webhook structure
The webhook will be sent with HMAC authentication. You must verify the signature to ensure the notification comes from Pago46.
Webhook headers
POST /webhooks/pago46 HTTP/1.1
Host: your-merchant.com
Content-Type: application/json
Merchant-Key: <YOUR_MERCHANT_KEY>
Message-Date: 1704463200.123
Message-Hash: a1b2c3d4e5f6...
Webhook payload
- State: COMPLETED
- State: CANCELLED
{
"id": "01952d91-a0ff-7f57-8f4e-68d95be01122",
"order_type": "ForeignCurrencyOrder",
"country": "MX",
"price": "2895.00",
"price_currency": "MXN",
"description": "Cash withdrawal from USDC balance",
"merchant_order_id": "FX-PO-2026-0001",
"status": "COMPLETED",
"notify_url": "https://your-merchant.com/webhooks/pago46",
"redirect_url": "https://checkout.dev.pago46.io/01952d91-a0ff-7f57-8f4e-68d95be01122",
"return_url": "https://your-merchant.com/withdrawal/back",
"consumer_email": "user@example.com",
"consumer_phone_number": "",
"expiry": "2030-01-03T12:05:00Z",
"paid": "2030-01-01T12:10:00Z",
"quote": "01952d8f-8da1-7f4b-bb36-cfba8a3be829",
"transaction": "AgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADyTBSt8EsV++3pX0uVJVEfDjDp8mnxoqBQocHmFw2xTf62jiq5VGtOhOY4yZH7evGRh3UonSpGQKHYTU0EVXkCgAIBBxJXN/S1MQpq3laWmAefYKQbtIFHc3dbFnEnnIO8+VCswgaIfmL5OVR6yf1DpJxY0nPEwFUxZnKSpVis0mLyYvB+CadUk1OGINWUunRSHPvVZsHS9SxcAzkMV3l0YrJa87VfpoD7lSKyS7KXhZdFjn4tDD9PM7ZkSXL+bJrJU6WMj2H0hZpDLnPhzU1d1RhBedKdDIuLnQj09NPzfpJAUTubj9Njqmepn7FPWB/pRxyBJLcCwVALhYefbQnfHrKVYlqZDYlyLNK33Q83kGZK7y8fZH3WFOPQax4GGUN64gOvfJ3zTDIAyZPrGSRcP4/Qj+sgg0V135Yr0S5moofrpJa7uYWuPLoIHj1rtPdLNX3+MqAowL1C3mfnKmBZ/gHZOU2/2TY6lXGwBdMPZ/X4ph/o4AGrpI1iulm/FEsovQG37vM5zLFxniH7ltpr8uBf04yhJRTUvdC/74e97kFxMjiNAwZGb+UhFzL/7K26csOb57yM5bvF9xJrLEObOkAAAAAEedVb8jHAbu50xW7OaBUH/bGy3qP0jlECsc2iVrwTjwVKU1qZKSEGTSTocWDaOHx8NbXdvJK7geQfqEBBBUSNBt324ddloZPZy+FGzut5rBy0he1fWzeROoz1hX7/AKkG3fbh7nWP3hhCXbzkbM3athr8TYO5DSf+vfko2KGL/LQ/+if11/ZKdMCbHylYed5LCas238ndUUsyGqezjOXo4vh36D2uxoiPQ6u1rnsxG7eMNJYIl/zlp7gsFuIP8ZcyjRWyhzKZjkXq5kHeRVDaKaLEEPfSWQM7V8ohwWOR/wYNABhBWnljbytLbWRnbTNZZFN4Q1Z5SnFBPT0OBAYYCQAKDEBCDwAAAAAABgsABQLAXBUACwAJAwQXAQAAAAAADB0OEQEJCAcEGBcMDxAMFg8ODRETFxgHEggUAgMKFSbBIJszQdacgQABAAAALwAAZAABQEIPAAAAAADskeQAAAAAABQAAA8EBBcFAQoM7vXjAAAAAAAGAWdhe29wATESFFYXVPJ+3KgjZOfKxNroQxcYyDV2rOPsBJVakZgDllyS",
"wallet": "7EcDhSYGxXyscszYEp35KHN8vvw3svAuLKTzXwCFLtV"
}
{
"id": "01952d91-a0ff-7f57-8f4e-68d95be01122",
"order_type": "ForeignCurrencyOrder",
"country": "MX",
"price": "2895.00",
"price_currency": "MXN",
"description": "Cash withdrawal from USDC balance",
"merchant_order_id": "FX-PO-2026-0001",
"status": "CANCELLED",
"notify_url": "https://your-merchant.com/webhooks/pago46",
"redirect_url": "https://checkout.dev.pago46.io/01952d91-a0ff-7f57-8f4e-68d95be01122",
"return_url": "https://your-merchant.com/withdrawal/back",
"consumer_email": "user@example.com",
"consumer_phone_number": "",
"expiry": "2030-01-03T12:05:00Z",
"paid": null,
"quote": "01952d8f-8da1-7f4b-bb36-cfba8a3be829",
"transaction": "AgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADyTBSt8EsV++3pX0uVJVEfDjDp8mnxoqBQocHmFw2xTf62jiq5VGtOhOY4yZH7evGRh3UonSpGQKHYTU0EVXkCgAIBBxJXN/S1MQpq3laWmAefYKQbtIFHc3dbFnEnnIO8+VCswgaIfmL5OVR6yf1DpJxY0nPEwFUxZnKSpVis0mLyYvB+CadUk1OGINWUunRSHPvVZsHS9SxcAzkMV3l0YrJa87VfpoD7lSKyS7KXhZdFjn4tDD9PM7ZkSXL+bJrJU6WMj2H0hZpDLnPhzU1d1RhBedKdDIuLnQj09NPzfpJAUTubj9Njqmepn7FPWB/pRxyBJLcCwVALhYefbQnfHrKVYlqZDYlyLNK33Q83kGZK7y8fZH3WFOPQax4GGUN64gOvfJ3zTDIAyZPrGSRcP4/Qj+sgg0V135Yr0S5moofrpJa7uYWuPLoIHj1rtPdLNX3+MqAowL1C3mfnKmBZ/gHZOU2/2TY6lXGwBdMPZ/X4ph/o4AGrpI1iulm/FEsovQG37vM5zLFxniH7ltpr8uBf04yhJRTUvdC/74e97kFxMjiNAwZGb+UhFzL/7K26csOb57yM5bvF9xJrLEObOkAAAAAEedVb8jHAbu50xW7OaBUH/bGy3qP0jlECsc2iVrwTjwVKU1qZKSEGTSTocWDaOHx8NbXdvJK7geQfqEBBBUSNBt324ddloZPZy+FGzut5rBy0he1fWzeROoz1hX7/AKkG3fbh7nWP3hhCXbzkbM3athr8TYO5DSf+vfko2KGL/LQ/+if11/ZKdMCbHylYed5LCas238ndUUsyGqezjOXo4vh36D2uxoiPQ6u1rnsxG7eMNJYIl/zlp7gsFuIP8ZcyjRWyhzKZjkXq5kHeRVDaKaLEEPfSWQM7V8ohwWOR/wYNABhBWnljbytLbWRnbTNZZFN4Q1Z5SnFBPT0OBAYYCQAKDEBCDwAAAAAABgsABQLAXBUACwAJAwQXAQAAAAAADB0OEQEJCAcEGBcMDxAMFg8ODRETFxgHEggUAgMKFSbBIJszQdacgQABAAAALwAAZAABQEIPAAAAAADskeQAAAAAABQAAA8EBBcFAQoM7vXjAAAAAAAGAWdhe29wATESFFYXVPJ+3KgjZOfKxNroQxcYyDV2rOPsBJVakZgDllyS",
"wallet": "7EcDhSYGxXyscszYEp35KHN8vvw3svAuLKTzXwCFLtV"
}
Webhook verification
It's critical that you verify the authenticity of each received webhook to prevent processing fraudulent notifications.
Python verification example
import hmac
import hashlib
import json
from flask import Flask, request, jsonify
app = Flask(__name__)
# Your Merchant Secret (obtained from Pago46)
MERCHANT_SECRET = "your_merchant_secret_here"
@app.route('/webhooks/pago46', methods=['POST'])
def webhook_handler():
# 1. Extract headers
merchant_key = request.headers.get('Merchant-Key')
message_date = request.headers.get('Message-Date')
received_hash = request.headers.get('Message-Hash')
# 2. Get raw body
body_str = request.get_data(as_text=True)
# 3. Build string to sign
# Format: MERCHANT_KEY:MESSAGE_DATE:METHOD:PATH:BODY
method = request.method # "POST"
path = request.path # "/webhooks/pago46"
string_to_sign = f"{merchant_key}:{message_date}:{method}:{path}:{body_str}"
# 4. Calculate HMAC
calculated_hash = hmac.new(
MERCHANT_SECRET.encode('utf-8'),
string_to_sign.encode('utf-8'),
hashlib.sha256
).hexdigest()
# 5. Verify
if not hmac.compare_digest(calculated_hash, received_hash):
return jsonify({"error": "Invalid signature"}), 403
# 6. Process notification
order_data = json.loads(body_str)
order_id = order_data.get('id')
order_status = order_data.get('status')
merchant_order_id = order_data.get('merchant_order_id')
print(f"Order {merchant_order_id} ({order_id}) changed to status: {order_status}")
# Update your database
if order_status == 'COMPLETED':
# Mark as completed
paid_at = order_data.get('paid')
print(f"Withdrawal completed on: {paid_at}")
elif order_status == 'CANCELLED':
# Mark as cancelled
print("Withdrawal cancelled")
# 7. Respond with 200 OK
return jsonify({"status": "received"}), 200
if __name__ == '__main__':
app.run(port=5000)
Node.js verification example
const express = require('express');
const crypto = require('crypto');
const app = express();
const MERCHANT_SECRET = 'your_merchant_secret_here';
app.post('/webhooks/pago46', express.text({ type: '*/*' }), (req, res) => {
// 1. Extract headers
const merchantKey = req.headers['merchant-key'];
const messageDate = req.headers['message-date'];
const receivedHash = req.headers['message-hash'];
// 2. Body as string
const bodyStr = req.body;
// 3. Build string to sign
const method = req.method;
const path = req.path;
const stringToSign = `${merchantKey}:${messageDate}:${method}:${path}:${bodyStr}`;
// 4. Calculate HMAC
const calculatedHash = crypto
.createHmac('sha256', MERCHANT_SECRET)
.update(stringToSign)
.digest('hex');
// 5. Verify
if (calculatedHash !== receivedHash) {
return res.status(403).json({ error: 'Invalid signature' });
}
// 6. Process notification
const orderData = JSON.parse(bodyStr);
const { id, status, merchant_order_id, paid } = orderData;
console.log(`Order ${merchant_order_id} (${id}) changed to status: ${status}`);
if (status === 'COMPLETED') {
console.log(`Withdrawal completed on: ${paid}`);
// Update database
} else if (status === 'CANCELLED') {
console.log('Withdrawal cancelled');
// Update database
}
// 7. Respond
res.status(200).json({ status: 'received' });
});
app.listen(5000, () => {
console.log('Webhook server listening on port 5000');
});
You must respond with an HTTP 200 or 201 status code to confirm reception. If you don't respond successfully, we will retry sending the notification.
We retry on any 5XX, 408, or 429 response, and also on timeouts or connection errors (no response received), using exponential backoff. In production, up to 600 attempts over roughly 4 days, capped at 10 minutes between attempts. In sandbox, only 3 attempts over a few seconds, following the same backoff pattern.
If the notification for an already-completed order fails in a non-retryable way (for example, retries are exhausted), we will reach out to you through one of your already-established communication channels to help resolve any issues your users may be experiencing with their order.
Integration best practices
- Use an idempotent
merchant_order_idper business operation. - Handle status changes in a retry-tolerant manner.
- Do not assume
COMPLETEDimmediately after creating the order. - Show the user a countdown based on the quote window.
- After a POST to
/pay-out/, maintain a status like "Waiting for transaction" until you receive the webhook containing thetransactionfield. Only then proceed to collect a signature from the user or advance the flow. - Store the returned order
idso you can correlate the initial POST and the webhook notification. - In case of validation errors, always request a new quote before retrying. See Common Errors for the exact error formats returned by the API for validation and logic issues.
- Implement retries: If your webhook server is down, Pago46 will retry the delivery.
If you need support for new asset pairs (for example, additional source currencies or destination countries), contact your account executive or email contacto@pago46.com.