Skip to content

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/:gateway

The :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

  1. Verifies the signature over the rawBody (that's why you need rawBody: true). Invalid signature → 400.
  2. Translates the notification into a generic WebhookEvent (captured / failed).
  3. Matches the event to the payment by its gateway_ref.
  4. Enforces idempotency by (gateway, event_id): replaying the same event returns 200 without re-processing.
  5. Emits the corresponding domain event.

Domain events

Listen to them with @OnEvent from @nestjs/event-emitter:

ts
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):

StateMeaning
pendingCreated; waiting for the user to complete the payment
authorizedAuthorized but not captured
capturedSuccessfully charged (event payment.captured)
failedRejected 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.

json
{
  "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:

GatewayWhere redirect_url points
StripeTo Stripe's hosted Checkout
PayPalTo PayPal's approval page
RedsysTo 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.