Verify the webhook before you act on it
The most common way to lose money on a payment integration is to trust a request that looks like a payment notification but isn't. If your backend fulfills an order the instant it receives a POST to your webhook URL, anyone who learns that URL can forge a paid event and walk away with goods. halfin signs every webhook so you never have to trust the request body on its own.
Each webhook carries an HMAC signature computed over the raw payload with a secret only you and halfin hold. Your handler recomputes the HMAC over the bytes you received and compares it to the signature header. If they match, the event is authentic and untampered; if they don't, you reject it. The rule is simple and absolute: verify the signature first, then act — never the other way around. Treat an event whose signature you cannot verify as if it never arrived.
The events you receive are a fixed, documented set. Each one tells your backend exactly what changed so you can react without polling. Build your handler as a switch over the event type, verify the signature before the switch, and make every branch idempotent — the same event may be delivered more than once, and a correct handler treats a duplicate as a no-op.
| Event | What it means | What your handler typically does |
|---|---|---|
| invoice.confirming | Invoice went live; the rate is locked and the payment window is open. | Start a timer or show the payer the address, amount, and countdown. |
| invoice.paid | Confirmations met the per-chain threshold; the billed amount settled. | Fulfill the order — this is the only event that should release goods. |
| invoice.underpaid | A deposit arrived but fell short of the expected amount. | Request a top-up or settle partially under your own policy. |
| invoice.overpaid | A deposit exceeded the expected amount. | Record the excess and reconcile or refund per your policy. |
| invoice.expired | The payment window elapsed before a sufficient payment arrived. | Re-issue at the current rate if the customer still wants to pay. |
| payout.completed | An approved payout settled on-chain to its destination. | Mark the disbursement done in your own ledger. |
Scoped API keys keep a leak small
An API key that can do everything is a single point of catastrophic failure. If it leaks — in a log, a stack trace, a misconfigured environment file — whoever holds it can read your data and move your money. halfin keys carry scoped permissions so the damage from a leaked key is bounded by what that key was allowed to do, not by what your account can do.
Give each integration the narrowest key it needs. A service that only creates invoices doesn't need permission to initiate payouts; a read-only reporting job doesn't need write access at all. When a key is scoped tightly, a compromise is an incident you contain by rotating one key, not an emergency that touches every part of your account.
Operationally, this means treating keys like the credentials they are: store them in a secret manager rather than in source control, pass them only in the X-API-Key header over TLS, rotate them on a schedule and immediately on any suspicion, and keep separate keys for separate environments so a test key can never touch live funds. The platform gives you the scoping primitive; the hygiene around it is yours to run.
- Least privilege — issue a key only the permissions its integration actually uses.
- One key per integration — so you can rotate or revoke without breaking everything else.
- Header-only transport — keys travel in X-API-Key over TLS, never in a URL or a query string.
- Separate environments — never let a test key reach live funds or vice versa.
- Rotate on suspicion — a key that may have leaked is a key you replace now, not later.
Payouts move money behind an approval and an audit trail
Outbound money is where mistakes and fraud hurt most, because an on-chain payout is final. halfin makes a payout a deliberate, recorded act rather than a fire-and-forget call. A payout is created with a destination, currency, and amount, and it settles only after it has been approved — the approval is the control point between intent and an irreversible transfer.
Every step is recorded. The dashboard keeps an audit trail of who created a payout, who approved it, and when it settled, so a disbursement is always traceable back to a person and a moment. When something looks wrong after the fact, you are reading a history, not reconstructing one. A mass payout is a fan-out over the same single-payout primitive — each line carries its own idempotency key, so retrying a batch never double-sends a line that already went out, and each line is approved and audited as its own movement of money.
Idempotency is part of the safety model here, not just an ergonomics feature. Because every payout request carries an idempotency key, a network retry or a duplicated job can't quietly send the same money twice — halfin recognizes the repeated key and returns the original result instead of creating a second transfer.
Crediting is reorg-aware and waits for confirmations
A transaction that appears on a blockchain is not yet money you can rely on. Blocks can be reorganized — a chain can drop the block your payment was in and replace it with a competing one — and a payment that looked confirmed can be undone. An integration that credits on first sight will eventually credit a payment that later vanishes.
halfin marks an invoice paid only after confirmations reach a threshold chosen for the chain in question, because the chains differ in how quickly a block becomes hard to reverse. Bitcoin's risk profile is not Solana's, and the confirmation policy reflects that. Crediting is reorg-aware: if a reorganization unwinds a transaction halfin had been tracking, that is reflected rather than ignored. The amount you see as settled is an amount that actually held on-chain.
This is why invoice.paid is the event your fulfillment logic should hinge on. Earlier signals — a deposit seen, confirmations accumulating — are useful for keeping the payer informed, but they are not a settled outcome. Release goods on paid, and let the per-chain confirmation policy and reorg handling do the work of deciding when a payment is final.
Where custody begins and ends
Being precise about custody matters more than making sweeping promises about it. halfin's security model is built on three things it controls directly: it signs the events it sends so you can authenticate them, it enforces the permissions attached to your API keys so access is bounded, and it records who did what across payouts and money movement so actions are auditable. Those are mechanisms you can check.
halfin does not claim a custody guarantee beyond signing, permissions, and audit. The controls described here are mechanisms that exist and that you operate; the platform does not promise outcomes it cannot deliver on its own. When a control is in your hands — verifying a signature, scoping a key, approving a payout — the platform gives you the tool and you operate it. Security here is a shared responsibility with a clear seam.
Compliance sits alongside this as process, not as a badge. Know-your-business onboarding, anti-money-laundering awareness, and travel-rule handling are things halfin runs as procedures; none of them is a certification or a license, and halfin makes no regulatory claim. For the specifics of those processes, the compliance FAQ is the place to look.