Skip to Content
Documentación de integración del API de Seif. ¿Dudas? soporte@pagosripei.com
EndpointsWebhooks

Webhooks

Los webhooks le avisan a tu servidor, en tiempo real, cuando algo ocurre: un cobro aprobado, una tarjeta guardada, una sesión que expiró. Es la forma recomendada de reaccionar a los pagos (mejor que hacer polling).


Eventos disponibles

EventoCuándo se emite
charge.approvedUn cobro fue aprobado.
charge.declinedUn cobro fue rechazado por el banco/tarjeta.
charge.failedUn cobro no se pudo procesar.
card.storedSe almacenó una tarjeta tras un cobro aprobado.
card.deletedSe eliminó una tarjeta guardada.
session.otp_requestedLa sesión solicitó un OTP (flujos con segundo factor).
session.otp_confirmedEl OTP fue confirmado.
session.expiredLa sesión se cerró sin pagarse: venció (sale al llegar expiresAt, aunque nadie vuelva a abrirla), agotó sus intentos (maxFailures) o la expiraste tú (POST /v1/sessions/{reference}/expire).

Eventos opcionales — solo Credicard

EventoCuándo se emite
card.authentication_expiringLa verificación de una tarjeta guardada está por vencer (avisos a 15, 7 y 1 día).
card.authentication_expiredCredicard pidió verificar una tarjeta guardada de nuevo: no se puede cobrar sin el cliente hasta que pague una vez con ella.

Aplican solo si cobras con Credicard. Algunas tarjetas —el débito nacional, la Master Debit— piden una verificación del tarjetahabiente: un código de su banco (OTP) o un cargo temporal cuyo monto confirma. Credicard la vuelve a pedir cada 3 meses. Estos eventos te avisan para que le pidas al cliente un pago desde el link y la verificación se renueve.

Son opcionales: no vienen incluidos en «todos los eventos». Llegan solo a los endpoints que los listan en enabledEvents (o que los marcan en Webhooks en tu panel). Así una integración que no los conoce nunca recibe un tipo de evento inesperado.


Registrar un endpoint

POST /v1/webhooks/endpoints

Autenticación: clave secreta (Authorization: Bearer sk_…).

CampoTipoDescripción
urlstring (https)A dónde Seif enviará los eventos. Debe ser https.
enabledEventsstring[]Eventos a recibir. Omítelo o envía [] para recibir todos (salvo los opcionales de Credicard, que se piden por nombre).
descriptionstringNota interna (opcional).
curl -X POST https://api.seif.pagosripei.com/v1/webhooks/endpoints \ -H "Authorization: Bearer sk_test_tu_clave" \ -H "Content-Type: application/json" \ -d '{ "url": "https://api.mitienda.com/seif/webhooks", "enabledEvents": ["charge.approved", "charge.declined", "charge.failed"] }'

La respuesta incluye un secret (whsec_…), mostrado una sola vez. Guárdalo: con él verificas la firma de cada evento.


Autenticación saliente (opcional)

Además de la firma Seif-Signature (que va siempre), puedes pedir que Seif autentique la llamada a tu endpoint para que tu receptor la valide. Se configura con authType al crear o actualizar:

authTypeCamposHeader que Seif envía
none (default)—(solo la firma)
bearerauthTokenAuthorization: Bearer <authToken>
basicauthUsername, authPasswordAuthorization: Basic base64(usuario:contraseña)
api_keyauthHeaderName, authToken<authHeaderName>: <authToken>
oauth2oauthTokenUrl, oauthClientId, oauthClientSecret, oauthScope (opc.)Authorization: Bearer <token> — Seif obtiene el token con client_credentials y lo cachea
curl -X POST https://api.seif.pagosripei.com/v1/webhooks/endpoints \ -H "Authorization: Bearer sk_test_tu_clave" \ -H "Content-Type: application/json" \ -d '{ "url": "https://api.mitienda.com/seif/webhooks", "authType": "bearer", "authToken": "tu-token" }'

Las credenciales se guardan cifradas y nunca se devuelven: las respuestas solo indican hasAuthToken, hasBasicAuth, etc. Para rotarlas, envía el nuevo valor con PATCH. La firma HMAC sigue activa con cualquier authType.


Gestionar endpoints

GET /v1/webhooks/endpoints # listar GET /v1/webhooks/endpoints/{id} # ver uno PATCH /v1/webhooks/endpoints/{id} # actualizar (URL, eventos, estado, auth) DELETE /v1/webhooks/endpoints/{id} # eliminar

En PATCH, omite un campo para conservarlo; en una credencial, envía "" para borrarla. Cambiar status a disabled detiene los envíos sin borrar el endpoint (el historial de entregas se conserva).


Probar un endpoint (ping)

POST /v1/webhooks/endpoints/{id}/test

Encola un evento sintético webhook.test hacia ese endpoint (con su firma y autenticación), aunque esté inactivo o no suscrito a ese evento. Devuelve { deliveryId, eventId }; el resultado aparece en el historial de entregas. Úsalo para verificar conectividad, firma y autenticación sin esperar a una transacción real.


Formato del evento

Cada entrega es un POST con este cuerpo:

{ "id": "evt_1a2b3c...", "type": "charge.approved", "createdAt": "2026-06-09T18:05:12.000Z", "env": "production", "data": { "transactionReference": "txn_2nFp9Zk", "sessionReference": "cs_8sKd2pQ1Aa", "amountMinor": 105000, "currency": "VES", "status": "approved", "declineReason": null, "errorCode": null, "processorReference": "000000000004663" } }

Cuerpo de data según el evento:

Eventodata
charge.*{ transactionReference, amountMinor, currency, status, declineReason, errorCode, processorReference }
card.stored{ seifToken, brand, lastFour, customerRef }
card.authentication_expiring{ seifToken, brand, lastFour, customerRef, gateway, authenticationStatus, authenticatedAt, estimatedExpiresAt, daysLeft }
card.authentication_expired{ seifToken, brand, lastFour, customerRef, gateway, authenticationStatus, authenticatedAt, estimatedExpiresAt, transactionReference }
session.expired{ sessionReference, reason, failureCount, maxFailures }

Los eventos charge.* traen además el origen del cobro: sessionReference si vino del checkout, o seifToken si fue un cobro con tarjeta guardada.

En los eventos card.authentication_*, estimatedExpiresAt es una estimación de Seif (tres meses después de la última verificación): quien decide es Credicard, y un cobro antes de esa fecha puede pedir verificación igual. transactionReference es el cobro que Credicard rechazó pidiendo la verificación.

En session.expired, reason es expired (venció por tiempo), max_failures_reached (agotó sus intentos) o merchant_request (la expiraste tú): los mismos valores que closedReason en Sesiones de pago. maxFailures es null si la sesión no tenía límite de intentos.

Por qué falló un cobro

En charge.declined y charge.failed vienen los dos campos que explican el resultado, así no hace falta consultar la transacción aparte:

CampoPara qué
declineReasonTexto en español, apto para mostrarle al comprador.
errorCodeCódigo del procesador. Es el que debes usar para ramificar en tu código — el texto puede cambiar.

Ambos son null en charge.approved.

Y al revés: en charge.approved viene processorReference, la referencia que asigna el banco. Es la que te sirve para conciliar contra tu estado de cuenta. En un rechazo va null, porque no hubo autorización que referenciar.

Si necesitas más detalle del que trae el webhook (el motivo técnico completo, la traza de eventos), consulta GET /v1/transactions/{reference} con la transactionReference del evento.


Verificar la firma

Cada entrega trae el header Seif-Signature:

Seif-Signature: t=1717954512,v1=5f8e...c3

Recalcula HMAC-SHA256 sobre `${t}.${cuerpoCrudo}` con tu secret (whsec_…) y compara con v1. Verifica además que t sea reciente para rechazar reenvíos.

import crypto from 'crypto'; function verifySeifWebhook(rawBody, header, secret) { const parts = Object.fromEntries(header.split(',').map((p) => p.split('='))); const expected = crypto .createHmac('sha256', secret) .update(`${parts.t}.${rawBody}`, 'utf8') .digest('hex'); const ok = crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(parts.v1)); const fresh = Math.abs(Date.now() / 1000 - Number(parts.t)) < 300; // 5 min return ok && fresh; }

Verifica la firma usando el cuerpo crudo (sin parsear). Si tu framework parsea el JSON antes, re-serializar cambiará los bytes y la firma no coincidirá.


Entregas y reintentos

  • Responde 2xx rápido (idealmente < 5 s). Cualquier otra respuesta se considera fallida.
  • Seif reintenta hasta 5 veces con backoff exponencial.
  • Puedes consultar el historial de entregas y reintentar manualmente:
GET /v1/webhooks/deliveries GET /v1/webhooks/deliveries/{id} POST /v1/webhooks/deliveries/{id}/retry

Diseña tu endpoint para ser idempotente: usa el id del evento (evt_…) para no procesar dos veces el mismo evento si llega repetido tras un reintento.

Last updated on