Documentación · API v1
Cobra en tu web con validación bancaria al instante
Tres formas de integrar, de menos a más código: el plugin de WooCommerce, el checkout alojado (rediriges y nosotros hacemos el resto), o la API REST completa. En todas, el pedido se confirma cuando el banco confirma — nunca antes.
Autenticación
Crea tu llave en tu panel (API → Crear llave). Se muestra una sola vez: guárdala en tu servidor. Nunca la pongas en el navegador de tus clientes ni en el código de tu tienda visible al público.
Base: https://armorpay.net/api/v1 Autorización: Authorization: Bearer ak_live_... Límites: 60 peticiones/min por llave · 15 intentos/5 min por IP en validación de referencia. Al superarlos: 429 con Retry-After.
Cobros (intents)
Un intent es un cobro que esperas recibir. Lo creas server-to-server con el monto que TÚ decides — la validación compara contra ese monto, nunca contra lo que declare el cliente final. Vence a los 30 minutos.
POST/api/v1/intents
GET/api/v1/intents/{id}
POST /api/v1/intents
Authorization: Bearer ak_live_...
Idempotency-Key: pedido-8812 # obligatorio: único por pedido
Content-Type: application/json
{
"externalRef": "8812", # el id del pedido en TU sistema
"amountVES": "1450.00", # máx. 2 decimales; string o número
"concepto": "Tienda X pedido 8812" # opcional, ≤40 tras sanear
}
# ¿Tus precios están en dólares? Manda amountUSD EN VEZ de amountVES: congelamos
# el monto en Bs con la tasa BCV del momento, y la validación acepta
# también USD × tasa vigente (el que paga con la tasa de hoy no falla).
# { "externalRef": "8812", "amountUSD": "25.00" }
# → el intent trae además amountUSD y exchangeRateUsed.
→ 201
{
"intent": {
"id": "cmm...", # úsalo para validar o redirigir a /pay
"externalRef": "8812",
"amountVES": "1450.00",
"concepto": "Tienda X pedido 8812",
"method": null, # REFERENCIA | C2P al confirmarse
"status": "PENDING", # PENDING | CONFIRMED | FAILED | EXPIRED
"referencia": null,
"overpaidVES": null,
"expiresAt": "2026-08-06T21:30:00.000Z",
"confirmedAt": null,
"createdAt": "2026-08-06T21:00:00.000Z"
}
}
# Reintentar con la MISMA Idempotency-Key devuelve el mismo intent (200):
# un timeout de red nunca duplica un cobro.Validar una referencia
Tu cliente ya pagó por pago móvil a tu cuenta y te da los últimos dígitos de la referencia de su comprobante. Nosotros confirmamos que el pago existe, alcanza y no se usó antes — el mismo árbitro antifraude que usan las cajas físicas.
POST/api/v1/intents/{id}/validate-reference
{
"referencia": "789123" # 6 a 20 dígitos, del comprobante
}
→ 200 (confirmado)
{
"intent": { ... "status": "CONFIRMED", "method": "REFERENCIA" ... },
"pago": {
"referencia": "000000789123",
"banco": "BDT", # banco receptor
"bancoPagador": "0134 · Banesco",
"montoVES": "1450.00",
"overpaidVES": null, # sobrepago aceptado y registrado
"fecha": "2026-08-06",
"hora": "153000"
}
}
Reglas de monto: se acepta un faltante de hasta max(1 Bs, 0.5%).
Subpago → 422 INSUFFICIENT_AMOUNT (con faltanteVES).
Sobrepago → se confirma y queda en overpaidVES.
Referencia ya cobrada (en caja o por otro intent) → 409 REFERENCE_ALREADY_USED.Cobro C2P (Botón de Pago)
Cobro activo: tu cliente genera una clave de pago desde su banco y el débito ocurre al instante. Requiere que tu comercio tenga C2P habilitado (se tramita con nosotros).
POST/api/v1/intents/{id}/c2p
{
"celular": "04121234567", # 04(12|14|16|24|26) + 7 dígitos
"bancoPagador": "0102", # del catálogo C2P (ver Bancos)
"cedula": "V12345678",
"otp": "12345678" # clave dinámica que generó tu cliente
}
→ 200 confirmado: { "intent": {...CONFIRMED...}, "cobro": { "referencia", "montoComision", ... } }
→ 422 C2P_REJECTED: rechazo del banco, con "hint" en español y
"retriable": true — puedes reintentar con una clave nueva
mientras el intent no venza.
→ 502 BANK_UNAVAILABLE: el banco no respondió. NO asumas rechazo:
verifica con tu cliente antes de reintentar.
El monto y el concepto salen del intent — el body nunca los lleva.Tasa BCV
Fija tus precios con la misma tasa con la que nosotros congelamos y validamos: cero discrepancias entre tu carrito y el cobro.
GET/api/v1/exchange-rate
→ 200
{ "currency": "USD/VES", "rate": "168.4200", "source": "BCV",
"fetchedAt": "2026-08-06T14:00:00.000Z" }
# Sin tasa utilizable: 503 RATE_UNAVAILABLE — nunca inventamos una.Cumplimiento en Venezuela
Si tu catálogo muestra precios en divisas, la norma exige que el precio en bolívares esté exhibido y que la conversión sea a tasa oficial BCV, con la moneda y la tasa claramente informadas — nunca una tasa paralela, y nunca precios distintos según el método de pago. Nuestra página de pago ya lo resuelve en el paso de cobro (Bs como monto principal + «Ref. USD … · tasa oficial BCV …»); para tu catálogo, usa este endpoint y muestra ambos. Esto es una guía, no asesoría legal.
Bancos
GET/api/v1/banks
GET /api/v1/banks # lista BCV — para mostrar el banco pagador
GET /api/v1/banks?service=c2p # catálogo PROPIO del C2P — para poblar el
# select de un cobro C2P (sus códigos no
# siempre coinciden con los del BCV)
→ 200 { "service": "...", "banks": [{ "code": "0102", "name": "..." }] }Checkout alojado (la vía rápida)
Si no quieres construir el formulario: crea el intent y redirige (o abre en iframe) nuestra página de pago. Muestra tu razón social y tu logo, guía al cliente por referencia o C2P, y confirma con las mismas reglas de la API.
Redirección: https://armorpay.net/pay/{intentId}
En iframe, te avisamos por postMessage:
window.addEventListener("message", (e) => {
const a = e.data?.armorpay;
if (a?.event === "confirmed") { /* pedido pagado: a.intentId, a.externalRef */ }
if (a?.event === "expired") { /* venció sin pagar */ }
});
# La confirmación de VERDAD llega por webhook (abajo) o consultando
# GET /intents/{id}: nunca confíes solo en el postMessage del navegador.¿Usas WooCommerce? Nuestro plugin hace todo esto por ti: pide el archivo al equipo de ArmorPay, configura tu llave y tu webhook, y listo.
Webhooks firmados
Registra tu URL en tu panel (API → Webhooks) y te avisamos a tu servidor cada confirmación o vencimiento — con firma, para que verifiques que fuimos nosotros. Si tu servidor no responde 2xx, reintentamos con espera creciente: 1 min, 5 min, 30 min, 2 h y 12 h.
POST a tu URL
x-armorpay-timestamp: 1754516096 # epoch en segundos
x-armorpay-signature: hex(HMAC-SHA256(secreto, timestamp + "." + body))
{ "event": "intent.confirmed", # o "intent.expired"
"intent": { ...la misma forma de la API... } }
— Verificación en Node.js —
const crypto = require("node:crypto");
function verificar(secreto, timestamp, firma, bodyCrudo) {
if (Math.abs(Date.now() / 1000 - Number(timestamp)) > 300) return false;
const esperada = crypto.createHmac("sha256", secreto)
.update(timestamp + "." + bodyCrudo).digest("hex");
return crypto.timingSafeEqual(Buffer.from(esperada), Buffer.from(firma));
}
— Verificación en PHP —
function verificar($secreto, $timestamp, $firma, $bodyCrudo) {
if (abs(time() - (int)$timestamp) > 300) return false;
$esperada = hash_hmac("sha256", $timestamp . "." . $bodyCrudo, $secreto);
return hash_equals($esperada, $firma);
}
# Usa el body CRUDO (antes de parsear el JSON): re-serializarlo
# cambia bytes y la firma deja de coincidir.Errores
Toda respuesta de error trae un code estable (programa contra él) y un message en español (muéstralo si te sirve).
| HTTP | code | Qué hacer |
|---|---|---|
| 401 | UNAUTHORIZED | Revisa la llave: inválida, inactiva o el comercio no está activo. |
| 429 | RATE_LIMITED | Espera lo que diga Retry-After y reintenta. |
| 400 | IDEMPOTENCY_KEY_REQUIRED | Manda el header Idempotency-Key al crear intents. |
| 400 | VALIDATION / INVALID_AMOUNT | El body no cumple el formato; el detalle viene en issues. |
| 404 | INTENT_NOT_FOUND | Ese intent no existe (o no es tuyo). |
| 410 | INTENT_EXPIRED | Venció: crea un intent nuevo. |
| 404 | PAYMENT_NOT_FOUND | El pago aún no llegó. Espera 1-2 min y reintenta. |
| 422 | INSUFFICIENT_AMOUNT | Subpago: faltanteVES dice cuánto falta. |
| 409 | AMBIGUOUS_REFERENCE | Pide más dígitos de la referencia. |
| 409 | REFERENCE_ALREADY_USED | Ese pago ya se cobró; cobradoPor dice dónde. |
| 422 | C2P_NOT_ENABLED | El comercio no tiene C2P habilitado todavía. |
| 422 | C2P_REJECTED | El banco rechazó: muestra hint y permite clave nueva. |
| 502 | BANK_UNAVAILABLE | El banco no respondió: verifica antes de reintentar. |
| 422 | MERCHANT_NOT_READY | El comercio no tiene cuentas activas. |
| 503 | RATE_UNAVAILABLE | Sin tasa BCV utilizable: reintenta o cobra en VES. |
¿Algo no cuadra entre estas docs y la API? Es un bug nuestro — escríbenos desde la página de contacto.