Webhooks
Hensa sends a JSON POST to your URL whenever something happens. Every webhook is signed – always verify before trusting the data.
Events
Section titled “Events”bank_transaction.createdMoney arrived in a linked account (every transfer, including ones not tied to an order).dataBankTransactionpayment_request.paidA payment request was paid in full – this is the event to fulfil the order on.dataPaymentRequestpayment_request.partially_paidThe customer paid less than requested. Do not fulfil yet – ask for the remainder or handle it manually. Once fully paid you get `payment_request.paid`.dataPaymentRequestpayment_request.expiredExpired unpaid. Release reserved stock or create a new one.dataPaymentRequestbank_connection.status_changedA bank connection failed or recovered. Pause bank-transfer checkout if needed.dataBankConnectionStatus
Payload
Section titled “Payload”Every event shares the same envelope:
{ "id": "evt_01JD7QB2S8E6H4N1P9C3V7K5TQ", "type": "payment_request.paid", "createdAt": "2026-11-03T08:03:12Z", "data": { "id": "prq_01JD7Q8M4K2X9V3T6B5N0R1W8Y", "amount": 150000, "currency": "VND", "referenceCode": "LK7F3K9", "status": "paid", "externalOrderRef": "DH-1024", "…": "…" }}| Field | Meaning |
|---|---|
id |
Event id evt_… – use it to de-duplicate |
type |
Event type |
createdAt |
When the event happened |
data |
The object at that moment (see the API reference) |
Accompanying header:
Hensa-Signature: t=1762156992,v1=5257a869e7ecebeda32affa62cdca3fa51cad7e77a0e56ff536d0ce8e108d8bdResponding
Section titled “Responding”- Return a 2xx within 10 seconds. Anything else (or a timeout) counts as a failure and is retried.
- Heavy work (emails, courier calls…) → put it on a queue and return 200 right away.
- No response body is needed.
Retries
Section titled “Retries”Failed deliveries are retried on a fixed schedule – 8 attempts in total over about 45 hours:
| Attempt | Wait after previous | Since first attempt |
|---|---|---|
| 1 | — | 0 |
| 2 | 1 min | 1 min |
| 3 | 5 min | 6 min |
| 4 | 30 min | 36 min |
| 5 | 2 h | 2.6 h |
| 6 | 6 h | 8.6 h |
| 7 | 12 h | 20.6 h |
| 8 | 24 h | 44.6 h |
Hensa never silently switches off your webhook:
- After 3 consecutive failures the endpoint becomes
failingand Hensa emails the account owner and technical admins. - Only after 7 days of continuous failure is the endpoint paused (
disabled) – with advance email. - Every event is kept for 30 days – you can redeliver any of them, including events that happened while the endpoint was paused.
Handling duplicates
Section titled “Handling duplicates”The same event can arrive more than once (retries, manual redelivery, flaky networks). Record processed event.ids and skip repeats:
-- One small table; the UNIQUE constraint does the hard partCREATE TABLE processed_hensa_events (event_id text PRIMARY KEY, processed_at timestamptz DEFAULT now());Ordering
Section titled “Ordering”Hensa does not guarantee ordering between events. Don’t assume bank_transaction.created always precedes payment_request.paid – always read the status in data (or fetch the latest via the API).
Redelivery
Section titled “Redelivery”- Dashboard › Developer › Webhooks › Deliveries → “Redeliver” any event.
- Or the API:
POST /webhook_deliveries/{id}/redeliver. - Send a test event:
POST /webhook_endpoints/{id}/testor the “Send test event” button – instantly receive a signed sample to check your code.
URL security
Section titled “URL security”- Only public HTTPS is accepted. Private addresses (
localhost,10.x,192.168.x…) are rejected. - A hard-to-guess path segment (e.g.
/webhooks/hensa/8f3k…) is an extra layer – never a replacement for signature verification.