Webhooks
Subscribe once, react to everything. Webhooks let your stack respond to bookings, payments, and member lifecycle events without polling.
Lifecycle
- Create a subscription via
POST /webhookswith your callback URL and the event types you care about. - Hapana stores a per-subscription signing secret and returns it once on creation. Save it — it can't be retrieved again, only rotated.
- When a matching event fires, Hapana POSTs the event payload to your URL with an
X-Hapana-Signatureheader. - Your endpoint returns
2xxwithin 10 seconds to acknowledge. Anything else (or a timeout) is a delivery failure.
Verifying the signature
Every delivery includes X-Hapana-Signature as an HMAC-SHA256 of the raw request body, hex-encoded, prefixed with the timestamp:
X-Hapana-Signature: t=1745601234,v1=4f8c3b...
X-Hapana-Delivery: del_2N7s8x9KqR
Content-Type: application/jsonReconstruct and compare in constant time:
import crypto from 'node:crypto'
function verify(rawBody: string, header: string, secret: string): boolean {
const parts = Object.fromEntries(header.split(',').map((p) => p.split('=')))
const signed = `${parts.t}.${rawBody}`
const expected = crypto.createHmac('sha256', secret).update(signed).digest('hex')
const provided = Buffer.from(parts.v1, 'hex')
const expectedBuf = Buffer.from(expected, 'hex')
if (provided.length !== expectedBuf.length) return false
return crypto.timingSafeEqual(provided, expectedBuf)
}Replay protection
Reject deliveries where t is more than 5 minutes from the current time. Combined with HMAC verification, this prevents an attacker who captures one delivery from replaying it later.
Retry schedule
Failed deliveries are retried with exponential backoff over 24 hours. After the final attempt, the delivery is marked permanently failed and surfaced in the admin app.
| Attempt | Delay after failure |
|---|---|
| 1 | immediate |
| 2 | 30 seconds |
| 3 | 2 minutes |
| 4 | 10 minutes |
| 5 | 1 hour |
| 6 | 4 hours |
| 7 | 12 hours |
| 8 | final, gives up |
Manually re-deliver any past event via POST /webhooks/{id}/deliveries/{deliveryId}/redeliver.
Idempotency on your side
Treat the X-Hapana-Delivery header as the unique ID for the delivery and de-duplicate against it. The same event may be delivered more than once during retries or operator-triggered redeliveries.
Event catalogue
The full list lives in the OpenAPI spec under the webhooks section. Common events:
client.created,client.updated,client.archivedbooking.created,booking.cancelled,booking.checkedIn,booking.noShowpayment.succeeded,payment.failed,payment.refundedpackage.purchased,package.cancelled,package.expiredsession.created,session.updated,session.cancelled
Rotating the signing secret
Call POST /webhooks/{id}/rotate-secret to issue a new secret. Hapana signs deliveries with both the old and new secret for a 24-hour overlap window so you can roll without dropping deliveries.
