Skip to content
Beta · The spec may still change before general availability. The API domain api.hensa.example is a placeholder. See changes →

Reconciliation

Every incoming transaction has a reconciliation status:

Value Meaning
matched Linked to a payment request or an order ref
pending Needs a human – usually a wrong transfer description
unmatched Confirmed not an order (e.g. a personal transfer, a refund)

Hensa looks for the referenceCode (e.g. LK7F3K9) in the transfer description. Found with the right amount → matched and payment_request.paid is emitted. Nothing else to do.

When a transaction is pending, link it to the right order in one of three ways:

# 1. To a Hensa payment request
curl -X POST https://api.hensa.example/v1/bank_transactions/btx_…/reconcile \
-H "Authorization: Bearer $HENSA_API_KEY" -H "Idempotency-Key: $(uuidgen)" \
-H "Content-Type: application/json" -d '{ "paymentRequestId": "prq_…" }'
# 2. To an order id in your own system
-d '{ "orderRef": "DH-1024" }'
# 3. Mark it as not an order
-d '{ "notAnOrder": true }'

Store staff can do the same in Dashboard › Transactions › To match – the result is identical.

Webhooks are the primary path, but networks and servers fail sometimes. Run a periodic job (e.g. every 15 minutes) that fetches new transactions and checks them against your orders:

let cursor = null
do {
const url = new URL('https://api.hensa.example/v1/bank_transactions')
url.searchParams.set('limit', '100')
if (cursor) url.searchParams.set('cursor', cursor)
const page = await (await fetch(url, { headers: { Authorization: `Bearer ${process.env.HENSA_API_KEY}` } })).json()
for (const tx of page.data) handle(tx)
cursor = page.nextCursor
} while (cursor)

Anything you haven’t recorded yet → handle it as if the webhook just arrived. Because your handling is idempotent, running it again is always safe.