Retiros en Moneda Extranjera (Pay Out)
Este módulo permite crear retiros (pay-out) en moneda local usando fondos de origen en moneda extranjera (por ejemplo, USDC → MXN).
Como Comercio, tu integración habla exclusivamente con Pago46. Pago46 gestiona internamente la cotización, el seguimiento de la recepción de fondos y la habilitación del retiro en efectivo.
Consulta los pares y países disponibles para moneda extranjera y firma cada petición según la guía de Autenticación.
Todas las rutas descritas a continuación son relativas a la URL base de la API:
/api/v1
Flujo General
- Creas una cotización para un par de activos (
POST /merchants/quotes/). - Tu usuario acepta la cotización en tu aplicación.
- Creas una orden de retiro foreign asociada a esa cotización.
- Tu usuario envía los fondos al address indicado por Pago46.
- Si el fondeo llega dentro de la ventana válida, la orden avanza a
READYy queda lista para continuar el flujo normal de retiro.
Nota: la generación y entrega de la transacción blockchain ocurre en segundo plano, nunca en la respuesta inmediata del POST. Siempre espera el webhook antes de continuar con la firma.
Requisitos Previos
- Credenciales de Comercio (
Merchant-KeyyMerchant-Secret). - Firma HMAC en cada request (
Message-Date,Message-Hash). notify_urlpública por HTTPS para cambios de estado.- Al menos uno entre
consumer_emailoconsumer_phone_numberal crear la orden.
Revisa la sección de Autenticación para la firma.
1) Crear Cotización
Solicita una cotización para el par de activos deseado.
Endpoint: POST /merchants/quotes/
Campos de Request
| Campo | Tipo | Requerido | Descripción |
|---|---|---|---|
blockchain | String | Sí | Red de fondeo (SOLANA, STELLAR, MONAD). |
send | Decimal | Sí | Monto que enviará el usuario. |
send_currency | String | Sí | Moneda de origen (ej. USDC). |
target_country | String | Sí | País de destino (MX, CL, etc.). |
target_currency | String | Sí | Moneda local objetivo (ej. MXN). |
Ejemplo de Request
curl -X POST "https://api.dev.pago46.io/api/v1/merchants/quotes/" \
-H "Merchant-Key: <TU_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"
}'
Respuesta (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"
}
Guarda el id de la cotización. Lo usarás en la creación de la orden foreign.
La creación de la cotización puede fallar con 400 si:
- tu cuenta no está configurada para el país o la moneda de destino
(
No matching configuration found for merchant, country, and currency), - el par no es válido para la blockchain
(
<send_currency>:<target_currency> is not a valid pair for <blockchain> blockchain), - el monto a enviar es demasiado bajo (
The amount to send is too low.).
424)Si en ese momento no podemos asegurar un tipo de cambio justo, la cotización
responde 424 con A dependent operation failed.. Es una protección, no un
error de tu integración: solo emitimos la cotización cuando la tasa cumple
nuestras garantías, de modo que tu usuario nunca queda con una tasa
desfavorable.
Es una condición temporal y puede afectar a una blockchain en particular
mientras la liquidez se normaliza. Si la recibes, reintenta en unos minutos;
si una red sigue respondiendo 424, ofrece a tu usuario otra de las blockchains
soportadas (SOLANA, STELLAR, MONAD) como ruta alternativa.
2) Crear Orden de Retiro Foreign
Cuando tu usuario acepte la cotización, crea la orden de pay-out foreign usando
el quote.
Endpoint: POST /merchants/orders/pay-out/
Campos de Request
| Campo | Tipo | Requerido | Descripción |
|---|---|---|---|
order_type | String | Sí | Debe ser ForeignCurrencyOrder. |
quote | UUID | Sí | ID de la cotización creada previamente. |
description | String | Sí | Descripción de la transacción. |
merchant_order_id | String | Sí | ID único de tu sistema por Comercio. |
notify_url | URL | Sí | URL para notificaciones de estado. |
return_url | URL | Sí | URL de retorno. |
expiry | DateTime | Sí | Expiración de la orden (ISO 8601). |
consumer_email | String | Condicional | Requerido si no envías teléfono. |
consumer_phone_number | String | Condicional | Requerido si no envías email. |
wallet | String | Sí | Wallet que firmará la transacción on-chain. |
Ejemplo de Request
curl -X POST "https://api.dev.pago46.io/api/v1/merchants/orders/pay-out/" \
-H "Merchant-Key: <TU_MERCHANT_KEY>" \
-H "Message-Date: <TIMESTAMP>" \
-H "Message-Hash: <HMAC_SIGNATURE>" \
-H "Content-Type: application/json" \
-d '{
"quote": "01952d8f-8da1-7f4b-bb36-cfba8a3be829",
"description": "Retiro cash desde balance USDC",
"merchant_order_id": "FX-PO-2026-0001",
"notify_url": "https://tu-comercio.com/webhooks/pago46",
"return_url": "https://tu-comercio.com/retiro/volver",
"consumer_email": "usuario@ejemplo.com",
"expiry": "2030-01-01T12:05:00Z",
"wallet": "7EcDhSYGxXyscszYEp35KHN8vvw3svAuLKTzXwCFLtV",
"order_type": "ForeignCurrencyOrder"
}'
La respuesta de este POST retorna 201 Created:
- La orden ha sido creada, pero NO INCLUYE la transacción blockchain por firmar.
- Pago46 genera la transacción de forma asíncrona.
- Cuando la transacción on-chain esté lista, recibirás un webhook de actualización de la orden: el
statusseguirá siendoCREATED, pero ahora el payload incluirá el campotransactioncon los datos para firmar. - Es decir, puedes recibir múltiples webhooks con el mismo
status(CREATED) pero con distinto contenido; considera este webhook como el evento de "transaction ready". - ¡No intentes firmar ni mostrar datos de blockchain al usuario hasta recibir este webhook!
Después de crear la orden y recibir el 201 Created, tu integración debe esperar la notificación asincrónica por webhook. Solo cuando llegue el webhook con la transacción, el usuario podrá firmar on-chain.
Respuesta inmediata del POST (sin transacción):
{
"id": "01952d91-a0ff-7f57-8f4e-68d95be01122",
"order_type": "ForeignCurrencyOrder",
"country": "MX",
"price": "2895.00",
"price_currency": "MXN",
"description": "Retiro cash desde balance USDC",
"merchant_order_id": "FX-PO-2026-0001",
"status": "CREATED",
"notify_url": "https://tu-comercio.com/webhooks/pago46",
"redirect_url": "https://checkout.dev.pago46.io/01952d91-a0ff-7f57-8f4e-68d95be01122",
"return_url": "https://tu-comercio.com/retiro/volver",
"consumer_email": "usuario@ejemplo.com",
"consumer_phone_number": "",
"expiry": "2030-01-01T12:05:00Z",
"paid": null,
"transaction": "",
"quote": "01952d8f-8da1-7f4b-bb36-cfba8a3be829",
"wallet": "7EcDhSYGxXyscszYEp35KHN8vvw3svAuLKTzXwCFLtV"
}
Ejemplo de webhook: aquí la orden sí incluye el campo transaction:
{
"id": "01952d91-a0ff-7f57-8f4e-68d95be01122",
"order_type": "ForeignCurrencyOrder",
"country": "MX",
"price": "2895.00",
"price_currency": "MXN",
"description": "Retiro cash desde balance USDC",
"merchant_order_id": "FX-PO-2026-0001",
"status": "CREATED",
"notify_url": "https://tu-comercio.com/webhooks/pago46",
"redirect_url": "https://checkout.dev.pago46.io/01952d91-a0ff-7f57-8f4e-68d95be01122",
"return_url": "https://tu-comercio.com/retiro/volver",
"consumer_email": "usuario@ejemplo.com",
"consumer_phone_number": "",
"expiry": "2030-01-01T12: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) Firmar y enviar la transacción on-chain
Cuando llega el webhook con el campo transaction, ese valor es la transacción
sin firmar que Pago46 ya construyó. El formato depende de la red de la
cotización (base64, XDR, JSON, etc.) — revisa el detalle de cada red más
abajo. Tu usuario debe firmarla con la wallet que indicaste al crear la
orden y enviarla a la red para depositar los fondos en el address de Pago46.
El procedimiento es el estándar de cada red; no necesitas lógica propia de Pago46:
- Decodifica/parsea el campo
transactionsegún el formato de la red (ver detalle abajo). - Deserialízalo con el SDK de la blockchain correspondiente.
- Fírmalo con la
walletdel usuario. - Haz broadcast a la red y espera la confirmación.
Cuando la transacción se confirma on-chain y Pago46 valida la recepción de los
fondos, la orden avanza a READY y lo notificamos por webhook.
Según la red de la cotización:
- Solana — la transacción es una
VersionedTransaction. Deserialízala desde base64, fírmala con el keypair de la wallet y envíala con un RPC de Solana. Ver @solana/web3.js y la documentación de Solana. - Stellar — la transacción es un sobre XDR (
TransactionEnvelope). Decodifícalo, fírmalo con la clave de la cuenta y envíalo a Horizon. Ver el SDK de Stellar para JS y la documentación de Stellar. - Monad — es compatible con EVM. El campo
transactionllega como un objeto JSON con los parámetros de la transacción (no en base64). Pásalo a tu librería habitual (ethers.js o viem) para firmarla y enviarla como cualquier transacción de Ethereum. Ver la documentación de Monad.
Sandbox opera sobre la testnet de las tres blockchains (Solana, Stellar y Monad); Producción sobre mainnet. Apunta la wallet y el RPC a la red correcta según el ambiente. Revisa los ambientes para las URLs base de cada uno.
En Sandbox usamos un token de prueba no minteable para los activos de origen
(por ejemplo, USDC). Para probar en testnet necesitas que te entreguemos saldo
de ese token: solicítalo a tu ejecutivo comercial o escríbenos a
contacto@pago46.com.
4) Consultar Orden
Puedes consultar la orden de retiro cuando necesites verificar su estado.
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: <TU_MERCHANT_KEY>" \
-H "Message-Date: <TIMESTAMP>" \
-H "Message-Hash: <HMAC_SIGNATURE>"
Estados de la Orden (Foreign Pay-Out)
| Estado | Descripción | ¿Qué significa para el comercio? |
|---|---|---|
CREATED | Orden creada exitosamente | La orden se ha registrado y está pendiente de asignación a un proveedor |
READY | Fondos validados; orden lista para ser procesada | La recepción de fondos fue validada; la orden puede ser tomada por un proveedor para el retiro |
PAYMENT_STARTED | Pago en proceso | Un proveedor ha bloqueado la orden y está procesando el retiro |
COMPLETED | Completada | El retiro se completó exitosamente. El beneficiario recibió el efectivo |
CANCELLED | Cancelada | La orden fue cancelada y no se procesará |
EXPIRED | Expirada | La orden expiró y no se procesará |
Diagrama de Transición de Estados
Buenas Prácticas de Integración
- Usa
merchant_order_ididempotente por operación de negocio. - Maneja cambios de estado de forma tolerante a reintentos.
- No asumas
COMPLETEDinmediatamente después de crear la orden. - Presenta al usuario una cuenta regresiva según la ventana de cotización.
- Después del POST, mantén un estado "Esperando transacción" hasta recibir el webhook con el campo
transaction. - Almacena el
idde la orden devuelta para correlacionar el POST inicial y el webhook subsiguiente. - Ante errores de validación, solicita una nueva cotización antes de reintentar. Consulta la sección Manejo de errores comunes para más detalles sobre el formato de los mensajes de error devueltos por la API.
Si necesitas soporte para nuevos pares (por ejemplo, activos de origen o países adicionales), contacta a tu ejecutivo comercial o escríbenos a contacto@pago46.com.