Contenido
Guía de integración
Integra cobros con QR en tu sistema
Tu sistema (ERP, tienda, membresías...) crea cobros con una llamada a nuestra API, muestra a su cliente el QR o el enlace de pago y recibe la confirmación cuando se paga.
Lo que te entregamos
| Dato | Ejemplo | Para qué |
|---|---|---|
| URL base | https://pagos.sentra.com.pe | Todas las peticiones |
| API Key | sk_live_TU_API_KEY | Autenticar cada petición (secreta) |
| Secreto del webhook | whsec_TU_SECRETO | Verificar los avisos que te enviamos (secreto) |
| Ambiente | test o live | Dónde cobra tu cuenta (ver TEST y LIVE) |
La API Key y el secreto se muestran una sola vez al generarlos. Guárdalos en la configuración segura de tu servidor, nunca en código de navegador ni de apps móviles. Si se filtran, pídenos unos nuevos.
Inicio rápido
Dos recorridos, y en ambos tu sistema solo conversa con nuestra API:
1 · Crear el cobro
2 · Confirmar el pago
- Crea el pago con
POST /api/v1/payments, tuX-API-Keyy unaIdempotency-Key. - Muestra el cobro a tu cliente: el
checkout_urlo el QR. - Recibe el webhook
payment.completed, verifica su firma y marca tu orden como pagada. - Si dudas, consulta el pago con
GET /api/v1/payments/{payment_id}.
Autenticación
Envía tu API Key en la cabecera X-API-Key de cada petición, siempre por HTTPS
y siempre desde tu servidor.
X-API-Key: sk_live_TU_API_KEY
Todas las respuestas tienen la forma {"success": bool, "message": str, "data": ...}
e incluyen la cabecera X-Request-ID: indícala si nos reportas un problema.
Las claves empiezan siempre por sk_live_, también en pruebas: el prefijo
no indica el ambiente.
Crear un pago
POST/api/v1/payments
curl -X POST "https://pagos.sentra.com.pe/api/v1/payments" \
-H "X-API-Key: sk_live_TU_API_KEY" \
-H "Idempotency-Key: ORD-12345" \
-H "Content-Type: application/json" \
-d '{"amount": "25.00", "reference": "ORD-12345", "description": "Pedido #12345"}'
| Campo | Obligatorio | Descripción |
|---|---|---|
amount | Sí | Texto con hasta 2 decimales: "25.00". Mínimo 1.00. |
reference | Sí | Tu identificador del cobro (orden, factura...). Hasta 100 caracteres. |
currency | No | Solo PEN (por defecto). |
description | No | Hasta 255 caracteres. |
metadata | No | Hasta 20 pares clave-valor de texto, para tu uso. |
Cualquier otro campo se rechaza. Respuesta 201:
{
"success": true,
"message": "Pago creado correctamente",
"data": {
"id": "pay_xxxxx",
"status": "pending",
"amount": "25.00",
"currency": "PEN",
"reference": "ORD-12345",
"description": "Pedido #12345",
"metadata": {},
"qr_code": "000201...",
"qr_image": "data:image/svg+xml;base64,...",
"checkout_url": "https://...",
"expires_at": "2026-10-18T15:15:00-05:00",
"paid_at": null,
"payer": null,
"failure_code": null,
"environment": "live",
"created_at": "2026-10-18T15:00:00-05:00",
"updated_at": "2026-10-18T15:00:00-05:00"
}
}
Guarda data.id: es el payment_id del pago en esta API.
Mostrar QR / checkout
El pago nace en pending. Estos campos de la respuesta te sirven para cobrar y conciliar:
checkout_url- Página de pago lista para abrir o compartir. Muestra el QR, el monto y la referencia.
qr_code- Contenido del QR, para dibujarlo con tu propia librería.
qr_image- El QR ya dibujado como imagen
data:, para un<img src="...">. expires_at- Vencimiento del QR. Usa siempre el valor que devuelve la API: no
asumas una duración fija. Si vence sin pagarse, el pago pasa a
expiredy creas uno nuevo. id(payment_id)- Identificador del pago en esta API (
pay_xxxxx). Es el que usas para consultarlo y el que llega en los webhooks comopayment_id. reference- Tu propio identificador, tal como lo enviaste. Te sirve para encontrar tu orden.
environmenttestolive. Ver TEST y LIVE.
Es un QR interoperable procesado mediante TAYPI: se paga con Yape, Plin o la app de cualquier banco conectado a la CCE. Solo soles.
Idempotencia
La cabecera Idempotency-Key es obligatoria al crear un pago (1 a 255
caracteres visibles, sin espacios; por ejemplo el id de tu orden). Si se corta la
conexión, hay un timeout o recibes 503, repite la petición con la
misma clave y los mismos datos: obtienes el mismo pago, nunca uno nuevo.
| Situación | Respuesta |
|---|---|
| Primera vez | 201 con el pago nuevo |
| Misma clave y mismos datos | 200, el mismo pago y la cabecera Idempotent-Replayed: true |
| Misma clave con otros datos | 422: usa otra clave para otro cobro |
| Misma clave con la primera aún en curso | 409: reintenta en unos segundos |
Consultar un pago
GET/api/v1/payments/{payment_id}
curl "https://pagos.sentra.com.pe/api/v1/payments/pay_xxxxx" \
-H "X-API-Key: sk_live_TU_API_KEY"
Devuelve el pago con la misma forma que al crearlo. Solo ves tus propios pagos: un id ajeno responde 404.
Listar pagos
GET/api/v1/payments
curl "https://pagos.sentra.com.pe/api/v1/payments?status=completed&reference=ORD-12345&limit=50&offset=0" \
-H "X-API-Key: sk_live_TU_API_KEY"
Filtros opcionales: status y reference. Paginación con
limit y offset.
Estados de pago
| Estado | Significado |
|---|---|
| pending | QR emitido, esperando el pago |
| completed | Pagado y confirmado; trae paid_at y payer |
| expired | El QR venció sin pagarse |
| failed | Rechazado; ver failure_code (GATEWAY_UNCONFIRMED: la pasarela nunca confirmó el cobro; crea otro pago) |
| cancelled | Cancelado antes de pagarse |
| created | Registrado pero sin confirmar con la pasarela: repite el POST con la misma Idempotency-Key |
completed es definitivo. Un pago expired o failed solo puede
pasar después a completed si la pasarela confirma el cobro.
Webhooks
Si activamos el webhook de tu cuenta (nos das una URL https pública de tu
sistema), te enviamos un POST cuando uno de tus pagos llega a
payment.completed, payment.failed, payment.expired o
payment.cancelled. Así no necesitas consultar el pago en bucle.
POST https://tu-sistema.com/webhooks/pagos
Content-Type: application/json; charset=utf-8
X-Pagos-Event-Id: evt_xxxxx
X-Pagos-Event-Type: payment.completed
X-Pagos-Timestamp: 1760886245
X-Pagos-Signature: v1=5f2b...
X-Pagos-Attempt: 1
| Cabecera | Contenido |
|---|---|
X-Pagos-Event-Id | Id único del evento (evt_...), igual a id del cuerpo |
X-Pagos-Event-Type | Tipo de evento, igual a type del cuerpo |
X-Pagos-Timestamp | Segundos Unix del envío |
X-Pagos-Signature | v1= + firma HMAC-SHA256 (ver Verificación HMAC) |
X-Pagos-Attempt | Número de intento: 1, 2, ... |
Cuerpo (se envía compacto, en una línea; aquí formateado):
{
"created_at": "2026-10-19T15:04:05.123456+00:00",
"data": {
"amount": "25.00",
"currency": "PEN",
"environment": "live",
"expires_at": "2026-10-19T15:18:00+00:00",
"failure_code": null,
"metadata": {},
"paid_at": "2026-10-19T15:04:01+00:00",
"payment_id": "pay_xxxxx",
"reference": "ORD-12345",
"status": "completed"
},
"environment": "live",
"id": "evt_xxxxx",
"type": "payment.completed"
}
Responder y reintentos
- Responde
2xxen menos de 10 segundos. Si tienes trabajo pesado, encólalo y responde ya. - Cualquier otra respuesta, un timeout o un error de red se reintenta tras 1 min, 5 min, 15 min, 30 min, 1 h, 2 h, 4 h, 8 h y 12 h: 10 intentos en total. Las redirecciones no se siguen.
- Por los reintentos, un evento puede llegar más de una vez: deduplica por
id. - Si dudas de un evento, confírmalo con
GET /api/v1/payments/{payment_id}. - El cuerpo nunca incluye credenciales, API Keys ni secretos.
Verificación HMAC
X-Pagos-Signature: v1=HMAC-SHA256(webhook_secret, "<timestamp>.<raw_body>")
- Lee el cuerpo crudo (los bytes exactos), antes de parsear el JSON.
- Rechaza si
X-Pagos-Timestampdifiere más de 5 minutos de tu reloj. - Calcula el HMAC con tu secreto y compáralo en tiempo constante con cada valor
v1=. - Si no coincide, responde
400o401y no proceses nada. - Usa
idpara no procesar dos veces el mismo evento.
import hashlib, hmac, time
WEBHOOK_SECRET = "whsec_TU_SECRETO" # desde tu configuracion segura
def verificar(cuerpo: bytes, firma: str, timestamp: str, secreto: str) -> bool:
if abs(time.time() - int(timestamp)) > 300:
return False
esperada = hmac.new(secreto.encode(), timestamp.encode() + b"." + cuerpo, hashlib.sha256).hexdigest()
return any(hmac.compare_digest(esperada, p[3:]) for p in firma.split(",") if p.startswith("v1="))
// Express: express.raw({ type: "application/json" }) en esta ruta
const crypto = require("crypto");
const WEBHOOK_SECRET = "whsec_TU_SECRETO"; // desde tu configuracion segura
function verificar(cuerpo, firma, timestamp, secreto) {
if (Math.abs(Date.now() / 1000 - Number(timestamp)) > 300) return false;
const esperada = crypto.createHmac("sha256", secreto).update(`${timestamp}.`).update(cuerpo).digest("hex");
return firma.split(",").some(p => p.startsWith("v1=") && p.length === 67 &&
crypto.timingSafeEqual(Buffer.from(p.slice(3)), Buffer.from(esperada)));
}
<?php
// $cuerpo = file_get_contents('php://input');
const WEBHOOK_SECRET = 'whsec_TU_SECRETO'; // desde tu configuracion segura
function verificar(string $cuerpo, string $firma, string $timestamp, string $secreto): bool {
if (abs(time() - (int) $timestamp) > 300) return false;
$esperada = hash_hmac('sha256', $timestamp . '.' . $cuerpo, $secreto);
foreach (explode(',', $firma) as $parte) {
if (str_starts_with($parte, 'v1=') && hash_equals($esperada, substr($parte, 3))) return true;
}
return false;
}
TEST y LIVE
La URL y la API Key son las mismas en ambos ambientes. El ambiente lo fija la configuración
de tu cuenta, que administramos nosotros: las cuentas nuevas empiezan en test y
pasan a live cuando tu integración está lista.
Cobros de prueba. No mueven dinero real.
Cobros reales.
environment: compruébalo antes de dar una orden
por pagada (por ejemplo, ignora en producción un evento test).
Errores HTTP
| HTTP | Significado | Qué hacer |
|---|---|---|
400 | Datos inválidos o falta la Idempotency-Key | Revisa cuerpo y cabeceras |
401 | API Key inválida o revocada | Revisa la cabecera o pide una clave nueva |
403 | Cuenta o servicio no habilitado | Contáctanos |
404 | El pago no existe o no es tuyo | Revisa el id |
409 | Otra petición con la misma Idempotency-Key en curso | Reintenta en unos segundos |
422 | Clave reutilizada con otros datos, o cobro rechazado | Usa otra clave o corrige el monto |
429 | Demasiadas peticiones | Espera lo que indique Retry-After |
503 | Pasarela o cuenta de cobro no disponible | Reintenta con la misma Idempotency-Key |
Checklist de integración
- La API Key vive solo en el servidor y viaja en
X-API-Key. - Cada cobro usa una
Idempotency-Keyestable (el id de tu orden) y los reintentos la repiten. - Muestras
checkout_urlo el QR, y usas elexpires_atque devuelve la API. - El webhook verifica la firma sobre el cuerpo crudo y el timestamp, y deduplica por
id. - Respondes
2xxrápido y procesas en segundo plano. - Compruebas
environmentantes de dar una orden por pagada. - Probaste en
testantes de pedir el paso alive.