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).

HTTPcodeQué hacer
401UNAUTHORIZEDRevisa la llave: inválida, inactiva o el comercio no está activo.
429RATE_LIMITEDEspera lo que diga Retry-After y reintenta.
400IDEMPOTENCY_KEY_REQUIREDManda el header Idempotency-Key al crear intents.
400VALIDATION / INVALID_AMOUNTEl body no cumple el formato; el detalle viene en issues.
404INTENT_NOT_FOUNDEse intent no existe (o no es tuyo).
410INTENT_EXPIREDVenció: crea un intent nuevo.
404PAYMENT_NOT_FOUNDEl pago aún no llegó. Espera 1-2 min y reintenta.
422INSUFFICIENT_AMOUNTSubpago: faltanteVES dice cuánto falta.
409AMBIGUOUS_REFERENCEPide más dígitos de la referencia.
409REFERENCE_ALREADY_USEDEse pago ya se cobró; cobradoPor dice dónde.
422C2P_NOT_ENABLEDEl comercio no tiene C2P habilitado todavía.
422C2P_REJECTEDEl banco rechazó: muestra hint y permite clave nueva.
502BANK_UNAVAILABLEEl banco no respondió: verifica antes de reintentar.
422MERCHANT_NOT_READYEl comercio no tiene cuentas activas.
503RATE_UNAVAILABLESin 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.