Webhooks
Webhooks let you receive real-time notifications when events occur in your account. Peak Gateway delivers an HTTP POST to your endpoint with a signed JSON payload.
Registering an endpoint
POST /api/v1/webhooks
Authorization: Bearer <API_KEY>
Content-Type: application/json
{
"url": "https://yourapp.com/webhooks/gateway",
"events": [
"payment.completed",
"payment.declined",
"subscription.billed"
]
}
Verifying signatures
Every webhook request includes X-Gateway-Signature and X-Gateway-Timestamp headers. Verify both to ensure the payload came from Peak Gateway and is fresh:
import express from 'express';
import { WebhookVerifier } from '@gateway/sdk';
const verifier = new WebhookVerifier(process.env.WEBHOOK_SECRET);
app.post('/webhooks/gateway', express.raw({ type: 'application/json' }), async (req, res) => {
const signature = req.headers['x-gateway-signature'] as string;
const timestamp = req.headers['x-gateway-timestamp'] as string;
if (!signature || !timestamp) {
return res.status(400).send('Missing signature or timestamp');
}
const rawBody = req.body instanceof Buffer ? req.body.toString('utf8') : String(req.body ?? '');
try {
const event = await verifier.verify(rawBody, signature, timestamp);
// handle event...
res.sendStatus(200);
} catch (err) {
return res.status(401).send('Invalid signature');
}
});
The signature is an HMAC-SHA256 hex digest of <timestamp>.<rawBody> using your webhook secret. Requests with timestamps older than 5 minutes are rejected to prevent replay attacks.
Retry policy
Peak Gateway retries failed deliveries (non-2xx responses) with exponential backoff:
| Attempt | Delay |
|---|---|
| 1 | Immediate |
| 2 | 30 seconds |
| 3 | 5 minutes |
| 4 | 1 hour |
| 5 | 6 hours |
After 5 failed attempts the event is marked as FAILED and no further retries occur.
Return HTTP 2xx within 30 seconds. Timeouts are treated as failures.
Event types and payloads
All events share a common envelope:
{
"eventId": "evt_01j...",
"eventType": "payment.completed",
"timestamp": "2026-03-31T12:00:00Z",
"merchantId": "merch_123"
}
payment.completed
Fired when a transaction is successfully authorized and captured.
{
"eventId": "evt_01j9abc",
"eventType": "payment.completed",
"timestamp": "2026-03-31T12:00:00Z",
"transaction": {
"transactionId": "txn_01j9xyz",
"merchantId": "merch_123",
"type": "SALE",
"status": "APPROVED",
"amount": 2500,
"currency": "USD",
"cardBrand": "VISA",
"cardLast4": "1111",
"approvalCode": "TXN123",
"createdAt": "2026-03-31T12:00:00Z"
}
}
payment.declined
Fired when a transaction is declined by the issuer.
{
"eventId": "evt_01j9def",
"eventType": "payment.declined",
"timestamp": "2026-03-31T12:01:00Z",
"transaction": {
"transactionId": "txn_01j9abc",
"merchantId": "merch_123",
"type": "SALE",
"status": "DECLINED",
"amount": 2500,
"currency": "USD",
"responseCode": "D0001",
"responseMessage": "Do Not Honor",
"createdAt": "2026-03-31T12:01:00Z"
}
}
payment.voided
Fired when a transaction is voided before settlement.
{
"eventId": "evt_01j9ghi",
"eventType": "payment.voided",
"timestamp": "2026-03-31T13:00:00Z",
"transaction": {
"transactionId": "txn_01j9xyz",
"merchantId": "merch_123",
"type": "VOID",
"status": "APPROVED",
"amount": 2500,
"currency": "USD",
"parentTransactionId": "txn_01j9abc",
"createdAt": "2026-03-31T13:00:00Z"
}
}
payment.refunded
Fired when a refund is processed.
{
"eventId": "evt_01j9jkl",
"eventType": "payment.refunded",
"timestamp": "2026-03-31T14:00:00Z",
"transaction": {
"transactionId": "txn_refund_01",
"merchantId": "merch_123",
"type": "REFUND",
"status": "APPROVED",
"amount": 1000,
"currency": "USD",
"parentTransactionId": "txn_01j9abc",
"createdAt": "2026-03-31T14:00:00Z"
}
}
payment.settled
Fired when a transaction is included in a closed settlement batch.
{
"eventId": "evt_01j9mno",
"eventType": "payment.settled",
"timestamp": "2026-03-31T22:30:00Z",
"transaction": {
"transactionId": "txn_01j9abc",
"merchantId": "merch_123",
"amount": 2500,
"currency": "USD",
"settledAt": "2026-03-31T22:30:00Z"
}
}
checkout.expired
Fired when a checkout session expires without being completed.
{
"eventId": "evt_01j9pqr",
"eventType": "checkout.expired",
"timestamp": "2026-03-31T13:30:00Z",
"checkoutSessionId": "cs_01j9stu",
"merchantId": "merch_123",
"expiresAt": "2026-03-31T13:30:00Z"
}
subscription.created
{
"eventId": "evt_01j9vwx",
"eventType": "subscription.created",
"timestamp": "2026-03-31T10:00:00Z",
"subscription": {
"subscriptionId": "sub_01j9abc",
"merchantId": "merch_123",
"customerId": "cust_456",
"amount": 1999,
"currency": "USD",
"interval": "monthly",
"status": "ACTIVE",
"nextBillingDate": "2026-04-30"
}
}
subscription.billed
Fired when a recurring billing cycle completes successfully.
{
"eventId": "evt_01j9yza",
"eventType": "subscription.billed",
"timestamp": "2026-04-01T00:00:00Z",
"subscription": {
"subscriptionId": "sub_01j9abc",
"merchantId": "merch_123",
"customerId": "cust_456",
"amount": 1999,
"currency": "USD",
"cycle": 2,
"transactionId": "txn_01j9billing"
}
}
subscription.payment_failed
{
"eventId": "evt_01j9bcd",
"eventType": "subscription.payment_failed",
"timestamp": "2026-04-01T00:01:00Z",
"subscription": {
"subscriptionId": "sub_01j9abc",
"merchantId": "merch_123",
"customerId": "cust_456",
"amount": 1999,
"currency": "USD",
"failureReason": "CARD_DECLINED",
"retryCount": 1
}
}
subscription.cancelled
{
"eventId": "evt_01j9efg",
"eventType": "subscription.cancelled",
"timestamp": "2026-04-01T09:00:00Z",
"subscription": {
"subscriptionId": "sub_01j9abc",
"merchantId": "merch_123",
"customerId": "cust_456",
"cancelledAt": "2026-04-01T09:00:00Z"
}
}
subscription.past_due
Fired when a subscription enters past-due status after a failed billing attempt.
subscription.paused / subscription.resumed
Fired when a subscription is paused or resumed manually or programmatically.
All event types
| Event | Trigger |
|---|---|
payment.completed | Sale, auth-capture, or card-present transaction succeeds |
payment.declined | Transaction declined by issuer |
payment.voided | Transaction voided before settlement |
payment.refunded | Refund processed |
payment.settled | Transaction included in closed batch |
checkout.expired | Checkout session expires without payment |
subscription.created | New subscription created |
subscription.billed | Recurring billing cycle succeeds |
subscription.payment_failed | Recurring billing cycle fails |
subscription.past_due | Subscription enters past-due after failed retry |
subscription.cancelled | Subscription cancelled |
subscription.paused | Subscription paused |
subscription.resumed | Subscription resumed |