Webhook de confirmación
POST /webhooks/crezcamos/pse-payments
El nombre es histórico (nació para PSE liquidado por Crezcamos); hoy lo usan Cuenta Crezcamos y Bre-B. Un alias con un nombre neutro es una mejora opcional.
Estado de cada afirmación: Implementado (lo hace el motor y tiene pruebas), Decisión local (elegido por el equipo, modificable) o Por confirmar (depende de Crezcamos).
Autenticación y duplicados
- Cabecera
X-Crezcamos-Webhook-Secretcon el secreto compartido (CREZCAMOS__WEBHOOKSECRET); comparación en tiempo constante. Implementado. Sin firma del cuerpo ni timestamp. Por confirmar: firma HMAC y timestamp. - Identificador del evento:
event_idoiddel cuerpo, o la cabeceraX-Crezcamos-Event-Id; si no hay ninguno, el hash SHA-256 del cuerpo. Un identificador repetido se respondeduplicatesin reprocesar. Implementado.
Respuestas
| HTTP | Cuerpo | Cuándo |
|---|---|---|
200 | {"ok":true,"event_id":…,"payment_id":…,"payment_link_id":…} | Procesado |
200 | {"ok":true,"duplicate":true} | Evento ya recibido |
401 | {"ok":false,"error":"invalid signature"} | Secreto incorrecto o ausente |
422 | {"ok":false,"error":"payment not found"} | No se resolvió ni el pago, ni el link, ni el comercio |
422 | {"ok":false,"error":"amount mismatch"} / "currency mismatch" | El monto o la moneda no coinciden con el total del link |
Un 422 por monto no registra el evento: si lo reintentas con el mismo event_id vuelves a recibir 422, no duplicate. Implementado.
Cuerpo (JSON)
| Campo | Obligatorio | Uso |
|---|---|---|
payment_link_token (o link.token) | Sí, en el primer evento de un pago | Llave para resolver el link; el enlace que abrió el pagador ya lo lleva como token |
status (o status_detail, transaction.status) | Sí | Ver estados |
rail o payment_method | Recomendado | cuentas_crezcamos, breb o pse. Sin él (o desconocido) se asume pse, como antes |
provider_payment_id / payment_id | Recomendado | Identifica el pago en eventos posteriores |
provider_transaction_id / transaction_id | Opcional | Alternativa de búsqueda |
reference / provider_reference | Recomendado en el primer evento | Se guarda como referencia del pago |
amount, currency | Recomendado | Si vienen, deben coincidir con el total del link (COP; mayúsculas y escala decimal no importan). Si faltan, se usa el del link |
event_id | Recomendado | Deduplicación |
status_detail, reason, approval_code, response_code, failure_reason | Opcionales | Se guardan; los vacíos no borran lo anterior |
metadata | Opcional | Objeto libre; uno vacío no borra el anterior |
merchant_id | No usar | Se ignora si el link ya resolvió el comercio (anti-suplantación) |
Ejemplos
Primer evento (el pago nace aquí):
curl -s -X POST "$API/webhooks/crezcamos/pse-payments" \
-H "Content-Type: application/json" \
-H "X-Crezcamos-Webhook-Secret: $CREZCAMOS_WEBHOOK_SECRET" \
-H "X-Crezcamos-Event-Id: evt-0001" \
-d '{
"status": "PENDING",
"rail": "breb",
"payment_link_token": "D8bwL5EqTqOMOtO-IhIXm5NAEwTDMElp",
"provider_payment_id": "brb-9981",
"reference": "pedido-1042",
"amount": 50000,
"currency": "COP"
}'
Evento terminal mínimo (solo identifica el pago y trae el estado; no borra la referencia ni el metadata):
curl -s -X POST "$API/webhooks/crezcamos/pse-payments" \
-H "Content-Type: application/json" \
-H "X-Crezcamos-Webhook-Secret: $CREZCAMOS_WEBHOOK_SECRET" \
-H "X-Crezcamos-Event-Id: evt-0002" \
-d '{"status": "APPROVED", "provider_payment_id": "brb-9981"}'
Resolución del link (en este orden)
- El link del pago ya existente (por
provider_payment_idoprovider_transaction_id). payment_link_id/link.id.payment_link_token/link.token.referencetratada como token (limitación heredada:referenceno es el token del link, así que solo sirve si reenvías el token enreference).
Por eso el primer evento debe llevar payment_link_token.
Qué hace la pasarela con el evento
- Un pago nuevo nace con el
raildel evento y proveedorcrezcamos(Cuenta Crezcamos y Bre-B) oakua(PSE). Implementado. - Un valor nuevo no vacío reemplaza al guardado; uno vacío lo conserva. Implementado.
- Un
pendingtardío no degrada un pago ni un link yapaid; un contracargo sí. Implementado. - Tras procesarlo, dispara el webhook de pago al comercio. Implementado.
- Se asume entrega al menos una vez y posible desorden. Decisión local.