Acepta pagos cripto de ocho redes, con una sola integración.
ConexaPay genera una dirección de pago única por factura, verifica cada transacción directo en la cadena, cobra tu fee del mismo pago, y te avisa por webhook — sin que tengas que correr un nodo de nada.
Cómo funciona
Cada vez que tu sistema crea una factura, ConexaPay reserva una dirección de pago única para esa factura específica — nunca se reutiliza, generada por derivación determinística desde una sola semilla maestra. Tu cliente paga a esa dirección, en la red y moneda que elegiste, y en cuanto la transacción se confirma en la cadena:
- Marcamos la factura como
pagado - Movemos el monto neto (ya con el fee descontado) a tu wallet de liquidación
- Te avisamos por webhook, con reintentos automáticos si tu servidor no responde
Todo se valida directo en la blockchain — nunca confiamos en lo que diga un cliente sobre si pagó o no. No hay "modo de prueba" simulado: cada factura que crees es real, sobre la red real.
Guía rápida
De cero a tu primer pago recibido, en cinco pasos.
Descárgala en conexachain.com, o entra directo desde el navegador en wallet.conexachain.com — la necesitas para firmar tu solicitud, no hay usuario/contraseña.
Firmas con tu wallet, mandas tu nombre y tu wallet de liquidación — ver Solicitar una cuenta. Desde la wallet misma: Ajustes → ConexaPay.
El operador de la plataforma la revisa y aprueba desde su panel.
Tu llave y secreto se generan en ese momento — guárdalos, el secreto no se vuelve a mostrar.
POST /v1/invoices con la red, la moneda, y el monto — ver Crear una factura.
Así no dependes de estar consultando el estado tú mismo — ver Webhooks.
Así se ve para tu cliente
Con los datos que te devuelve la API (dirección, QR, monto, tiempo restante) puedes armar tu propia pantalla de cobro. Esto es un ejemplo de cómo se vería:
Envía exactamente el monto indicado, en Arbitrum One. Esta página se actualiza sola en cuanto detectemos el pago.
Cómo se construye
- Creas la factura con
POST /v1/invoices - Muestras
direccionPagoy el resultado deGET /v1/invoices/:id/qr - Consultas
GET /v1/invoices/:id/publicocada pocos segundos, o esperas el webhook - Cuando
estadocambia apagado, muestras la confirmación
Cómo funciona el fee
El fee es un porcentaje del mismo pago, en la misma moneda que usó tu cliente — no se convierte a ninguna otra moneda. Si alguien te paga 100 USDT y tu fee es 0.5%, tú recibes 99.5 USDT, directo en tu wallet de liquidación. No hay ninguna moneda intermedia, ni bóveda que tengas que prefondear.
- El fee por defecto es configurable por el operador de la plataforma, y puede tener un valor especial distinto para tu cuenta
- Se descuenta automáticamente en cada factura pagada — nunca tienes que hacer nada para que esto pase
- El campo
montoNetode cada factura es exactamente lo que llegó a tu wallet;montoFeees lo que se cobró - Puedes ver tu fee actual con
GET /v1/merchant/me, campofeePct
Ejemplo
Redes y monedas soportadas
Consulta GET /v1/networks en cualquier momento para ver la lista actualizada — puede cambiar si el operador habilita o pausa una red.
| Red | Código | Nativo | Tokens |
|---|---|---|---|
| Ethereum | ethereum | ETH | USDT, USDC |
| BNB Smart Chain | bsc | BNB | USDT, USDC |
| Arbitrum One | arbitrum | ETH | USDT, USDC |
| Polygon | polygon | MATIC | USDT, USDC |
| Conexa Chain | conexa | CONEXA | Cualquier token listado y activado en Market |
| Tron | tron | TRX | USDT (próximamente) |
curl https://pay.conexachain.com/v1/networks{
"success": true,
"data": {
"ethereum": { "chainId": 1, "nombre": "Ethereum", "nativo": "ETH", "monedas": ["ETH","USDT","USDC"] },
"arbitrum": { "chainId": 42161, "nombre": "Arbitrum One", "...": "..." }
}
}Pagar con un token listado en Market
Además de CONEXA, tus clientes pueden pagar con cualquier token que ya esté listado y activo en el Market de la wallet — no solo el token nativo de la cadena. Esto es exclusivo de red: "conexa": cada token vive solo en la cadena donde se listó, así que este método no aplica en Ethereum, BSC, Arbitrum, ni Polygon — ahí solo se paga con el nativo, USDT, o USDC de esa red.
moneda en una factura, el token debe estar en estado aprobado en el listado, y su creador ya debe haberlo activado (configurado precio inicial y liquidez) — mientras tanto, ese símbolo no aparece como opción válida.
Cómo saber qué tokens están disponibles ahora mismo
La lista es dinámica — cambia según lo que se vaya listando y activando. Consulta GET /v1/networks y mira el arreglo monedas de conexa: ahí vas a ver CONEXA más el símbolo de cada token activo en ese momento.
{
"success": true,
"data": {
"conexa": {
"chainId": 7797,
"nombre": "Conexa Chain",
"nativo": "CONEXA",
"monedas": ["CONEXA", "MITOKEN", "OTROTOKEN"]
}
}
}Crear una factura pagable en un token listado
Exactamente igual que cualquier otra factura — solo cambia el valor de moneda por el símbolo del token.
curl -X POST https://pay.conexachain.com/v1/invoices \
-u cxpk_tu_llave:cxsk_tu_secreto \
-H "Content-Type: application/json" \
-d '{
"red": "conexa",
"moneda": "MITOKEN",
"monto": 250,
"referencia": "ORDEN-8821"
}'Si el símbolo que mandas no está listado, no fue aprobado, o su creador aún no lo activó, la API responde con bad_request — igual que cualquier otra moneda no soportada.
Solicitar una cuenta
No hay registro por formulario web con usuario/contraseña — tu identidad es tu propia wallet. Cada solicitud, y cada acción de cuenta, se firma con tu wallet (igual que firmar una transacción), y el servidor recupera la dirección desde la firma misma, nunca confía en lo que le digas.
| Parámetro | Descripción |
|---|---|
| direccionrequerido | Tu dirección de wallet |
| mensajerequerido | El texto exacto que firmaste, incluyendo ts: con la marca de tiempo |
| firmarequerido | La firma de ese mensaje, con tu wallet |
| nombrerequerido | Nombre de tu negocio |
| walletLiquidacionrequerido | Dirección donde quieres recibir tus pagos ya netos |
| webhookUrlopcional | Puedes configurarlo después también |
// Con ethers.js -- tu propio signer de wallet
const mensaje = `Solicitar comerciante ts:${Date.now()}`;
const firma = await signer.signMessage(mensaje);
await fetch('https://pay.conexachain.com/v1/merchant-requests', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
direccion: await signer.getAddress(),
mensaje, firma,
nombre: 'Mi Negocio',
walletLiquidacion: '0xTuWalletAqui...',
})
});# Con web3.py -- tu propia cuenta local
from eth_account.messages import encode_defunct
import time, requests
mensaje = f"Solicitar comerciante ts:{int(time.time()*1000)}"
firma = account.sign_message(encode_defunct(text=mensaje)).signature.hex()
requests.post("https://pay.conexachain.com/v1/merchant-requests", json={
"direccion": account.address,
"mensaje": mensaje, "firma": firma,
"nombre": "Mi Negocio",
"walletLiquidacion": "0xTuWalletAqui...",
})// Con web3.php o similar para firmar
$mensaje = "Solicitar comerciante ts:" . (time() * 1000);
$firma = $wallet->signMessage($mensaje);
$ch = curl_init("https://pay.conexachain.com/v1/merchant-requests");
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, ["Content-Type: application/json"]);
curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode([
"direccion" => $direccion, "mensaje" => $mensaje, "firma" => $firma,
"nombre" => "Mi Negocio", "walletLiquidacion" => "0xTuWalletAqui...",
]));
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
$respuesta = curl_exec($ch);Consultar el estado de tu solicitud
Mismo patrón de firma que solicitar. El campo estado de la respuesta puede ser pendiente, aprobado, o rechazado.
{
"success": true,
"data": {
"id": "req_a1b2c3d4",
"estado": "aprobado",
"merchantId": null,
"notasAdmin": ""
}
}merchantId en null significa: ya te aprobaron, pero todavía no has reclamado tus credenciales.
Reclamar tus credenciales
Una vez aprobada tu solicitud, tu llave y tu secreto se generan justo en este momento — nunca antes. Esta es la única vez que verás el secreto en texto plano; guárdalo de inmediato, en un gestor de contraseñas o similar.
{
"success": true,
"data": {
"merchantId": "merch_9f8e7d6c",
"apiKey": "cxpk_...",
"apiSecret": "cxsk_..."
}
}Perdí mis credenciales
Si ya reclamaste antes pero perdiste el secreto (o es un dispositivo nuevo), puedes generar unas credenciales nuevas tú mismo, sin depender del operador — con la misma firma de wallet que usaste para solicitar la cuenta. El secreto anterior se invalida al instante.
Dos formas de probar, sin dinero real
Antes de pasar a producción, ConexaPay te da dos maneras de probar tu integración — puedes usar una, o las dos, según lo que necesites verificar.
| Simulación pura | Sandbox on-chain real | |
|---|---|---|
| Qué prueba | Tu código: cómo manejas la respuesta de la API y el webhook | El proceso completo, con una transacción real en la cadena |
| Red/moneda | Cualquiera de las soportadas | Solo Conexa Chain, con el token oficial de pruebas |
| Cómo se "paga" | Llamas un endpoint que marca la factura pagada | Mandas de verdad la ficha de prueba a la dirección |
| Necesita fichas | No | Sí — del grifo, gratis |
Activar sandbox
Mismo patrón de firma que solicitar una cuenta real, pero instantáneo. Tus credenciales llevan el prefijo cxpk_test_ / cxsk_test_ — inconfundibles con las de producción.
const mensaje = `Activar sandbox ts:${Date.now()}`;
const firma = await signer.signMessage(mensaje);
const r = await fetch('https://pay.conexachain.com/v1/sandbox/activar', {
method: 'POST', headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ direccion: await signer.getAddress(), mensaje, firma, nombre: 'Mi Negocio (prueba)' })
});
const { apiKey, apiSecret } = (await r.json()).data; // guardalos, el secreto no se repiteSi pierdes el secreto, POST /v1/sandbox/rotar (mismo patrón de firma) genera uno nuevo.
Simulación pura
Con tus credenciales de sandbox, crea una factura exactamente igual que en producción — red y moneda pueden ser cualquiera de las soportadas. En vez de esperar un pago real, llama este endpoint para marcarla pagada al instante. El fee se calcula con la misma fórmula que producción, y tu webhook se dispara idéntico (con "sandbox": true en los datos, para que nunca lo confundas con un pago real).
curl -X POST https://pay.conexachain.com/v1/invoices/inv_xxxxx/simular-pago \
-u cxpk_test_...:cxsk_test_...Opcionalmente manda {"montoRecibido": 30} en el cuerpo para simular un pago parcial y ver cómo reacciona tu código a eso.
Sandbox on-chain real
Cuando creas una factura con red: "conexa" y como moneda usas el símbolo del token oficial de pruebas (consulta cuál es con GET /v1/sandbox/token-prueba), la factura recibe una dirección real — el mismo vigilante que procesa pagos de producción la detecta, cobra el fee, hace el barrido, y dispara tu webhook. Todo real, excepto que el token no tiene valor de mercado.
El grifo de fichas
Pide fichas de prueba gratis, directo a tu wallet — mismo patrón de firma. Hay un tiempo de espera entre solicitudes por wallet (consultalo en GET /v1/sandbox/token-prueba, campo cooldownHoras).
Llave + secreto
Una vez que tienes tus credenciales, el resto de la API usa HTTP Basic Auth — tu llave pública y tu llave secreta, codificadas en base64, en la cabecera Authorization. Es el mismo estándar que usa cualquier librería HTTP de cualquier lenguaje, sin nada especial que instalar.
curl https://pay.conexachain.com/v1/merchant/me \
-u cxpk_tu_llave_publica:cxsk_tu_llave_secretaconst auth = Buffer.from(`${apiKey}:${apiSecret}`).toString('base64');
const res = await fetch('https://pay.conexachain.com/v1/merchant/me', {
headers: { 'Authorization': `Basic ${auth}` }
});import requests
r = requests.get(
"https://pay.conexachain.com/v1/merchant/me",
auth=("cxpk_tu_llave_publica", "cxsk_tu_llave_secreta")
)$ch = curl_init("https://pay.conexachain.com/v1/merchant/me");
curl_setopt($ch, CURLOPT_USERPWD, "cxpk_tu_llave:cxsk_tu_secreto");
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
$respuesta = curl_exec($ch);require 'net/http'
uri = URI("https://pay.conexachain.com/v1/merchant/me")
req = Net::HTTP::Get.new(uri)
req.basic_auth("cxpk_tu_llave", "cxsk_tu_secreto")
res = Net::HTTP.start(uri.host, uri.port, use_ssl: true) { |http| http.request(req) }req, _ := http.NewRequest("GET", "https://pay.conexachain.com/v1/merchant/me", nil)
req.SetBasicAuth("cxpk_tu_llave", "cxsk_tu_secreto")
resp, _ := http.DefaultClient.Do(req)Ver mi cuenta
Devuelve tus datos públicos.
Configurar mi webhook
curl -X PATCH https://pay.conexachain.com/v1/merchant/webhook \
-u cxpk_...:cxsk_... \
-H "Content-Type: application/json" \
-d '{"webhookUrl": "https://tunegocio.com/webhook"}'El objeto Factura
Esta es la forma completa que verás en todas las respuestas de facturas — creación, consulta, listado, y en el webhook.
inv_a1b2c3d4pendiente · pagado · expiradoCrear una factura
Genera una dirección de pago única. La factura expira sola si nadie paga dentro del tiempo configurado (30 minutos por defecto).
| Parámetro | Tipo | Descripción |
|---|---|---|
| redrequerido | string | Una de las redes soportadas — ver Redes y monedas |
| monedarequerido | string | La moneda nativa de esa red, o un token soportado |
| montorequerido | number | Monto exacto a cobrar, en la moneda elegida |
| referenciaopcional | string | Tu propio identificador — se te devuelve tal cual en el webhook |
| metadataopcional | object | Cualquier dato adicional tuyo, se guarda junto a la factura |
curl -X POST https://pay.conexachain.com/v1/invoices \
-u cxpk_tu_llave:cxsk_tu_secreto \
-H "Content-Type: application/json" \
-d '{
"red": "arbitrum",
"moneda": "USDT",
"monto": 49.90,
"referencia": "ORDEN-4471"
}'const factura = await fetch('https://pay.conexachain.com/v1/invoices', {
method: 'POST',
headers: {
'Authorization': `Basic ${auth}`,
'Content-Type': 'application/json'
},
body: JSON.stringify({
red: 'arbitrum', moneda: 'USDT', monto: 49.90,
referencia: 'ORDEN-4471'
})
}).then(r => r.json());r = requests.post(
"https://pay.conexachain.com/v1/invoices",
auth=("cxpk_tu_llave", "cxsk_tu_secreto"),
json={"red": "arbitrum", "moneda": "USDT", "monto": 49.90,
"referencia": "ORDEN-4471"}
)$ch = curl_init("https://pay.conexachain.com/v1/invoices");
curl_setopt($ch, CURLOPT_USERPWD, "cxpk_tu_llave:cxsk_tu_secreto");
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, ["Content-Type: application/json"]);
curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode([
"red" => "arbitrum", "moneda" => "USDT", "monto" => 49.90,
"referencia" => "ORDEN-4471",
]));
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
$respuesta = curl_exec($ch);require 'net/http'
require 'json'
uri = URI("https://pay.conexachain.com/v1/invoices")
req = Net::HTTP::Post.new(uri, "Content-Type" => "application/json")
req.basic_auth("cxpk_tu_llave", "cxsk_tu_secreto")
req.body = { red: "arbitrum", moneda: "USDT", monto: 49.90, referencia: "ORDEN-4471" }.to_json
res = Net::HTTP.start(uri.host, uri.port, use_ssl: true) { |http| http.request(req) }body, _ := json.Marshal(map[string]interface{}{
"red": "arbitrum", "moneda": "USDT", "monto": 49.90, "referencia": "ORDEN-4471",
})
req, _ := http.NewRequest("POST", "https://pay.conexachain.com/v1/invoices", bytes.NewBuffer(body))
req.SetBasicAuth("cxpk_tu_llave", "cxsk_tu_secreto")
req.Header.Set("Content-Type", "application/json")
resp, _ := http.DefaultClient.Do(req)Respuesta
{
"success": true,
"data": {
"facturaId": "inv_a1b2c3d4e5f6",
"red": "arbitrum",
"moneda": "USDT",
"montoSolicitado": 49.9,
"direccionPago": "0x71C7656EC7ab88b098defB751B7401B5f6d8976F",
"estado": "pendiente",
"montoRecibido": null,
"montoNeto": null,
"montoFee": null,
"referencia": "ORDEN-4471",
"creadoEn": 1786150000000,
"expiraEn": 1786151800000
}
}Consultar una factura
Usa esto para hacer polling de respaldo — aunque el webhook es la forma principal de enterarte, siempre puedes preguntar el estado directo. Si la factura no es tuya, o no existe, la respuesta es idéntica en ambos casos (404) — para no filtrar de quién es cada factura.
curl https://pay.conexachain.com/v1/invoices/inv_a1b2c3d4e5f6 \
-u cxpk_tu_llave:cxsk_tu_secretoListar tus facturas
Devuelve solo tus propias facturas, nunca las de otro comerciante.
| Parámetro | Descripción |
|---|---|
| estadoopcional | Filtra por pendiente, pagado, o expirado |
| limiteopcional | Cuántas devolver, por defecto 50 |
curl "https://pay.conexachain.com/v1/invoices?estado=pagado&limite=20" \
-u cxpk_tu_llave:cxsk_tu_secretoEstado público de una factura
Sin autenticación, a propósito — para que tu página de checkout pueda mostrarle el estado al comprador (que no tiene, ni debe tener, tus credenciales de API). Devuelve solo lo esencial: red, moneda, monto, dirección de pago, estado, y cuándo expira.
Código QR de la factura
Devuelve un PNG en base64 con la dirección de pago — listo para mostrar en una pantalla de checkout o una factura impresa.
<!-- La respuesta trae "qr" como data URL, listo para usar -->
<img src="data:image/png;base64,iVBORw0KG..." alt="Pagar factura" />Webhooks
Configura tu URL desde PATCH /v1/merchant/webhook. En cuanto una factura se paga, te mandamos un POST con el evento.
Cuerpo del evento
{
"evento": "factura.pagada",
"datos": {
"facturaId": "inv_a1b2c3d4e5f6",
"red": "arbitrum",
"moneda": "USDT",
"montoSolicitado": 49.9,
"montoRecibido": 49.9,
"montoNeto": 49.65,
"montoFee": 0.25,
"txHashPago": "0x8f3a...c21e",
"txBarrido": "0x2b7c...9a01",
"referencia": "ORDEN-4471"
},
"timestamp": 1786150200000
}Eventos disponibles
| Evento | Cuándo se dispara |
|---|---|
factura.pagada | Una factura se confirmó pagada en la cadena y ya se barrió a tu wallet |
Este es el único evento disponible hoy — el diseño del sistema ya soporta agregar más adelante sin romper lo existente.
Verificar la firma del webhook
Cada entrega incluye la cabecera X-ConexaPay-Signature — un HMAC-SHA256 del cuerpo exacto, firmado con tu webhookSecret.
const crypto = require('crypto');
function esFirmaValida(cuerpoCrudo, firmaRecibida, webhookSecret) {
const esperada = crypto
.createHmac('sha256', webhookSecret)
.update(cuerpoCrudo)
.digest('hex');
return crypto.timingSafeEqual(
Buffer.from(firmaRecibida), Buffer.from(esperada)
);
}import hmac, hashlib
def es_firma_valida(cuerpo_crudo, firma_recibida, webhook_secret):
esperada = hmac.new(
webhook_secret.encode(), cuerpo_crudo, hashlib.sha256
).hexdigest()
return hmac.compare_digest(firma_recibida, esperada)function esFirmaValida($cuerpoCrudo, $firmaRecibida, $webhookSecret) {
$esperada = hash_hmac('sha256', $cuerpoCrudo, $webhookSecret);
return hash_equals($esperada, $firmaRecibida);
}Reintentos
Si tu servidor no responde con un código 2xx, reintentamos hasta 6 veces, con espera creciente:
| Intento | Espera |
|---|---|
| 1 | Inmediato |
| 2 | 1 minuto |
| 3 | 5 minutos |
| 4 | 30 minutos |
| 5 | 2 horas |
| 6 | 6 horas |
Después del último intento, el evento queda registrado como agotado — la factura sigue pagada y consultable por la API en todo momento, aunque el webhook no se haya podido entregar.
Manejo de errores
Toda respuesta con error sigue el mismo formato:
{
"success": false,
"error": {
"code": "unauthorized",
"message": "Credenciales invalidas"
}
}| HTTP | Código | Significa |
|---|---|---|
| 401 | unauthorized | Llave/secreto inválidos, o firma inválida/expirada (más de 5 min) |
| 404 | not_found | La factura no existe, o no es tuya |
| 400 | bad_request | Faltan parámetros, algún valor es inválido, o la acción no aplica en el estado actual |
Seguridad
- Toda validación de pagos ocurre en la cadena — nunca se confía en un aviso del cliente
- Cada factura tiene una dirección de pago que nunca se reutiliza
- Tu llave secreta nunca se guarda en texto plano de nuestro lado — solo su huella
- Tus datos están completamente aislados de los de cualquier otro comerciante
- Tu identidad de comerciante está atada a tu wallet, no a una contraseña
- Los webhooks van firmados con HMAC
Preguntas frecuentes
¿Qué pasa si mi cliente paga menos de lo pedido?
¿Puedo cambiar mi wallet de liquidación?
¿Hay ambiente de pruebas (sandbox)?
¿Qué pasa si mi servidor de webhook está caído?
¿Cómo sé cuál es mi fee actual?
GET /v1/merchant/me, campo feePct. Si es null, usas el estándar de la plataforma.Qué sigue
Tron y USDT sobre Tron están en desarrollo — arquitecturalmente distinto a las redes EVM (usa su propio SDK), así que llega en su propia fase, no mezclado con lo que ya existe.