Webhooks & events
The webhook is the source of truth of a payment. The browser's redirect back confirms nothing: only the gateway's signed notification changes the payment's state.
Route
POST /webhooks/:gatewayThe :gateway identifies the gateway (stripe, redsys, paypal). The core routes the request to the matching gateway and calls its parseWebhook.
What the library does on receiving a webhook
- Verifies the signature over the
rawBody(that's why you needrawBody: true). Invalid signature →400. - Translates the notification into a generic
WebhookEvent(captured/failed). - Matches the event to the payment by its
gateway_ref. - Enforces idempotency by
(gateway, event_id): replaying the same event returns200without re-processing. - Emits the corresponding domain event.
Domain events
Listen to them with @OnEvent from @nestjs/event-emitter:
import { OnEvent } from '@nestjs/event-emitter'
import { PaymentEventPayload, PAYMENT_EVENTS } from '@kaizen/payments-gateway'
@Injectable()
export class MyListener {
@OnEvent('payment.captured')
onCaptured(p: PaymentEventPayload) { /* payment confirmed */ }
@OnEvent('payment.failed')
onFailed(p: PaymentEventPayload) { /* payment rejected */ }
}Payment states
A Payment goes through these states (PaymentStatus):
| State | Meaning |
|---|---|
pending | Created; waiting for the user to complete the payment |
authorized | Authorized but not captured |
captured | Successfully charged (event payment.captured) |
failed | Rejected or failed (event payment.failed) |
How you take the user to pay
Whatever the gateway, POST /payments/charge always returns the same shape and you always do the same thing: redirect the browser to redirect_url.
{
"payment_id": "b9c1…",
"gateway": "stripe",
"status": "pending",
"redirect_url": "https://…"
}What sits behind that redirect_url changes per gateway, but the library handles it, not you:
| Gateway | Where redirect_url points |
|---|---|
| Stripe | To Stripe's hosted Checkout |
| PayPal | To PayPal's approval page |
| Redsys | To a page the library itself serves, which auto-submits the signed form to the POS |
For Redsys and PayPal, after paying the browser comes back through the library (which finalizes the charge) and ends up at your success_url / cancel_url. Details on each gateway page: Stripe, Redsys and PayPal.