Event Types
All 13 subscribable webhook events with when they fire and their payload fields.
halfin delivers 13 merchant-subscribable webhook events. New canonical deliveries use the envelope structure { event_id, event, created_at, data }, where event_id is a stable UUID for idempotency.
For compatibility, event_id remains optional in invoice, late-payment, payout, and refund schemas because a retry of a legacy stored payload may omit it. balance.credited always includes event_id.
Invoice events
All invoice events use the InvoiceWebhookData payload:
| Field | Type | Required | Description |
|---|---|---|---|
invoice_id | uuid | yes | Invoice UUID |
external_id | string | yes | Your external reference (null if not set) |
currency | string | yes | Currency code |
network | string | no | Blockchain network of the immutable amount snapshot |
environment | string | yes | live or test |
amount_requested | string | yes | Requested amount (decimal string) |
amount_paid | string | yes | Total paid so far (decimal string) |
status | string | yes | Invoice status at event time |
amount_fiat | string | no | Fiat-nominated amount (fiat-denominated invoices only) |
fiat_currency | string | no | Fiat currency code (e.g. "USD") |
paid_at | datetime | no | Payment timestamp (invoice.paid and invoice.overpaid only) |
expires_at | datetime | no | Expiry timestamp (invoice.expired only) |
source | string | no | "static_address" for auto-created invoices |
static_address_id | uuid | no | Static address UUID (static address invoices only) |
gross_amount | string | no | Gross receivable before fees (decimal string). Present on invoice.paid and invoice.overpaid for passthrough credits. |
fee_total | string | no | Total service fee on the credit (decimal string) |
net_amount | string | no | Net amount credited to merchant balance (decimal string) |
fees | array | no | Per-fee breakdown: [{ type, amount, payer }]. Payer is merchant, payer, or halfin. |
reversed_amount | string | no | Source amount reversed by invoice.payment_reversed |
reversed_currency | string | no | Source currency of the reversed payment |
reversed_network | string | no | Source network of the reversed payment |
invoice.confirming
Fires when the first on-chain payment is detected for an invoice. Confirmations are still pending.
invoice.paid
Fires when the invoice amount is met and the required number of blockchain confirmations is reached. Safe to fulfill the order.
invoice.overpaid
Fires when the total amount received exceeds the requested amount. Treat the same as invoice.paid for fulfillment.
invoice.underpaid
Fires when the invoice expires after receiving a partial payment below the underpayment threshold.
invoice.expired
Fires when an invoice reaches its TTL without being fully paid.
invoice.late_payment
Fires when a payment arrives after the invoice has already expired. Uses LatePaymentWebhookData:
| Field | Type | Required | Description |
|---|---|---|---|
invoice_id | uuid | yes | Invoice UUID |
external_id | string | yes | Your external reference (null if not set) |
currency | string | yes | Currency code |
network | string | no | Blockchain network of the triggering late payment |
environment | string | yes | live or test |
amount_requested | string | yes | Original requested amount |
late_payment_amount | string | yes | Amount of the late payment (decimal string) |
amount_usd | string | no | Legacy USD-denominated requested amount, when available |
amount_fiat | string | no | Fiat-denominated requested amount, when available |
fiat_currency | string | no | Fiat currency code paired with amount_fiat |
status | string | yes | Invoice status (always "expired") |
source | string | no | "static_address" for auto-created invoices |
static_address_id | uuid | no | Static address UUID |
gross_amount | string | no | Gross receivable before fees (decimal string). Present for passthrough credits. |
fee_total | string | no | Total service fee on the late credit (decimal string) |
net_amount | string | no | Net amount credited to merchant balance (decimal string) |
fees | array | no | Per-fee breakdown: [{ type, amount, payer }] |
rate_applied | string | no | Crypto-to-USD rate used for a static-address credit |
rate_updated_at | datetime | no | Timestamp of the exchange rate used for a static-address credit |
rate_age_ms | integer | no | Age of the applied rate in milliseconds |
rate_stale | boolean | no | Whether the applied rate exceeded its freshness threshold |
invoice.payment_reversed
Fires when a previously confirmed payment is reversed due to a blockchain reorganization. The invoice amount_paid is recalculated. Canonical deliveries identify the affected source amount with reversed_amount, reversed_currency, and reversed_network. These fields remain optional for compatibility with legacy stored payloads.
Balance event
balance.credited
Fires only after the merchant's available balance is credited. Use this as the fulfillment signal when balance availability matters. Includes a stable event_id for idempotency.
| Field | Type | Required | Description |
|---|---|---|---|
invoice_id | uuid | yes | Invoice UUID |
payment_id | uuid | yes | Payment row UUID |
external_id | string | no | Your external reference |
outcome | string | yes | Balance credit outcome |
source_amount | string | yes | Original deposit amount |
source_currency | string | yes | Deposit currency |
source_network | string | yes | Deposit network |
credited_amount | string | yes | Amount added to merchant balance |
credited_currency | string | yes | Credited balance currency |
credited_network | string | yes | Credited balance network |
gross_amount | string | no | Gross amount before fees |
fee_amount | string | no | Platform service fee |
net_amount | string | no | Net credited amount |
Payout events
payout.completed
Fires when a payout is confirmed on-chain.
| Field | Type | Required | Description |
|---|---|---|---|
payout_id | uuid | yes | Payout UUID |
currency | string | yes | Currency code |
network | string | yes | Blockchain network |
environment | string | yes | live or test |
amount | string | yes | Base requested payout amount |
recipient_amount | string | yes | Amount sent to the recipient after customer-allocated charges |
merchant_debit_amount | string | yes | Total merchant debit including merchant-allocated charges |
destination | string | yes | Destination address |
tx_hash | string | yes | On-chain transaction hash |
network_fee | string | yes | Actual on-chain network fee |
fee_amount | string | yes | Platform payout fee |
payout_fee_allocation | string | yes | Whether the payout fee is allocated to customer or merchant |
configured_network_fee_amount | string | yes | Snapshotted commercial network charge |
network_fee_allocation | string | yes | Whether the commercial network charge is allocated to customer or merchant |
status | string | yes | Always "completed" |
payout.failed
Fires when a payout fails. Reserved funds are released back to the merchant balance.
| Field | Type | Required | Description |
|---|---|---|---|
payout_id | uuid | yes | Payout UUID |
currency | string | yes | Currency code |
network | string | yes | Blockchain network |
environment | string | yes | live or test |
amount | string | yes | Base requested payout amount |
recipient_amount | string | yes | Amount intended for the recipient after customer-allocated charges |
merchant_debit_amount | string | yes | Merchant debit reserved for the failed payout |
fee_amount | string | yes | Snapshotted platform payout fee |
payout_fee_allocation | string | yes | Whether the payout fee is allocated to customer or merchant |
configured_network_fee_amount | string | yes | Snapshotted commercial network charge |
network_fee_allocation | string | yes | Whether the commercial network charge is allocated to customer or merchant |
destination | string | yes | Destination address |
error_message | string | yes | Error description |
status | string | yes | Always "failed" |
Refund events
All refund events use RefundWebhookData:
| Field | Type | Required | Description |
|---|---|---|---|
refund_id | uuid | yes | Refund UUID |
invoice_id | uuid | yes | Invoice UUID being refunded |
payout_id | uuid | yes | Linked payout UUID |
currency | string | yes | Refunded currency |
network | string | yes | Refunded network |
environment | string | yes | live or test |
amount | string | yes | Base requested refund amount |
recipient_amount | string | yes | Amount sent to the customer after customer-allocated charges |
merchant_debit_amount | string | yes | Total merchant debit including merchant-allocated charges |
refund_fee | string | yes | Platform refund fee |
refund_fee_allocation | string | yes | Whether the refund fee is allocated to customer or merchant |
network_fee | string | yes | Actual on-chain network fee after completion |
configured_network_fee_amount | string | yes | Snapshotted commercial network charge |
network_fee_allocation | string | yes | Whether the commercial network charge is allocated to customer or merchant |
destination | string | yes | Destination address |
destination_tag | string | yes | Memo/tag if applicable; null when absent |
status | string | yes | pending, completed, or failed |
tx_hash | string | yes | Transaction hash when completed; null otherwise |
failure_reason | string | yes | Processor failure reason when failed; null otherwise |
error_message | string | no | Additional error description for refund.failed |
refund.created
Fires when a refund is created and the linked payout is queued for execution.
refund.completed
Fires when the linked payout completes on-chain. Includes tx_hash and the actual network fee.
refund.failed
Fires when the linked payout fails and the refund reservation is released back to the merchant balance.