Webhooks
Transaction hooks
Register a hook that runs before or after a transaction.
Transaction hooks let you plug custom logic into your transaction lifecycle, before or after a transaction is processed, without changing how Blnk records the ledger entry.
Register an endpoint for each stage you care about, and Blnk calls it with the transaction context so you can validate, enrich, or trigger side effects at the right moment.
Hook types
Blnk supports two hook types:
PRE_TRANSACTION: before a transaction is appliedPOST_TRANSACTION: after a transaction is applied
PRE_TRANSACTION runs before a transaction is applied.
Use it for validation, fraud checks, or enriching transaction data while the transaction is still in an early state, for example QUEUED.
{
"event": "transaction.applied",
"hook_type": "PRE_TRANSACTION",
"data": {
"precise_amount": 20000,
"amount": 200,
"precision": 100,
"transaction_id": "txn_9c2f1a8e-4b3d-4c91-9f2a-1a7b6c5d4e3f",
"parent_transaction": "",
"source": "bln_7f91a1ae-6073-4b7a-952c-23abf94a6634",
"destination": "bln_59b83b9c-842c-427f-91eb-43cdeaf5c01a",
"reference": "INV-2025-0008912",
"currency": "GBP",
"description": "Wallet top-up",
"status": "QUEUED",
"hash": "623e05ceb7ca7b02a9318092de7b75d8c668628e2db76c42b621b61055b10b3e",
"allow_overdraft": true,
"inflight": false,
"skip_queue": false,
"created_at": "2025-02-23T09:43:49.623494+01:00",
"scheduled_for": "0001-01-01T00:00:00Z",
"inflight_expiry_date": "0001-01-01T00:00:00Z"
}
}POST_TRANSACTION runs after a transaction is applied.
Use it for notifications, workflow updates, or forwarding data to external systems once the transaction has been processed.
{
"event": "transaction.applied",
"hook_type": "POST_TRANSACTION",
"data": {
"precise_amount": 20000,
"amount": 200,
"precision": 100,
"transaction_id": "txn_9c2f1a8e-4b3d-4c91-9f2a-1a7b6c5d4e3f",
"parent_transaction": "",
"source": "bln_7f91a1ae-6073-4b7a-952c-23abf94a6634",
"destination": "bln_59b83b9c-842c-427f-91eb-43cdeaf5c01a",
"reference": "INV-2025-0008912",
"currency": "GBP",
"description": "Wallet top-up",
"status": "APPLIED",
"hash": "623e05ceb7ca7b02a9318092de7b75d8c668628e2db76c42b621b61055b10b3e",
"allow_overdraft": true,
"inflight": false,
"skip_queue": false,
"created_at": "2025-02-23T09:43:49.623494+01:00",
"scheduled_for": "0001-01-01T00:00:00Z",
"inflight_expiry_date": "0001-01-01T00:00:00Z"
}
}Getting started
Register a hook
Register your webhook URL with the Hooks API:
curl -X POST "http://localhost:5001/hooks" \
-H "X-blnk-key: YOUR_MASTER_KEY" \
-H "Content-Type: application/json" \
-d '{
"name": "Pre-transaction validation",
"url": "https://api.example.com/blnk/hooks/pre",
"type": "PRE_TRANSACTION",
"active": true,
"timeout": 30,
"retry_count": 3
}'const response = await blnk.Hooks.create({
name: 'Pre-transaction validation',
url: 'https://api.example.com/blnk/hooks/pre',
type: 'PRE_TRANSACTION',
active: true,
timeout: 30,
retry_count: 3,
});hook, _, _ := client.Hooks.Create(
blnkgo.CreateHookRequest{
Name: "Pre-transaction validation",
URL: "https://api.example.com/blnk/hooks/pre",
Type: blnkgo.HookTypePreTransaction,
Active: true,
Timeout: 30,
RetryCount: 3,
},
)response = blnk.hooks.create({
"name": "Pre-transaction validation",
"url": "https://api.example.com/blnk/hooks/pre",
"type": "PRE_TRANSACTION",
"active": True,
"timeout": 30,
"retry_count": 3,
})ApiResponse<JsonNode> response = blnk.hooks().create(
CreateHookData.create()
.name("Pre-transaction validation")
.url("https://api.example.com/blnk/hooks/pre")
.type(HookType.PRE_TRANSACTION)
.active(true)
.timeout(30)
.retryCount(3)
);| Field | Description |
|---|---|
name | Human-readable label for operators and logs. |
url | Absolute HTTPS or HTTP URL that accepts requests from Blnk. |
type | Either PRE_TRANSACTION or POST_TRANSACTION. |
active | When false, Blnk skips this hook until you enable it again. |
timeout | Maximum time in seconds Blnk waits for your endpoint to respond per attempt. |
retry_count | Maximum additional attempts after a failed delivery. |
{
"hook_id": "hk_890bdbc4-467f-4670-8424-5667e41daf29",
"name": "Pre-transaction validation",
"url": "https://api.example.com/blnk/hooks/pre",
"type": "PRE_TRANSACTION",
"active": true,
"timeout": 30,
"retry_count": 3
}Test the hook
Create a transaction with. Blnk calls your hook URL when the transaction reaches the stage you registered, PRE or POST.
Confirm your endpoint receives an event with the expected payload and returns 2xx.
curl -X POST "http://localhost:5001/transactions" \
-H "X-blnk-key: <api-key>" \
-H "Content-Type: application/json" \
-d '{
"amount": 200,
"precision": 100,
"reference": "INV-2025-0008912",
"currency": "GBP",
"source": "@Funding",
"destination": "@UserWallet",
"description": "Wallet top-up"
}'const response = await blnk.Transactions.create({
amount: 200,
precision: 100,
reference: 'INV-2025-0008912',
currency: 'GBP',
source: '@Funding',
destination: '@UserWallet',
description: 'Wallet top-up',
});transaction, _, _ := client.Transaction.Create(
blnkgo.CreateTransactionRequest{
ParentTransaction: blnkgo.ParentTransaction{
Amount: 200,
Precision: 100,
Reference: "INV-2025-0008912",
Currency: "GBP",
Source: "@Funding",
Destination: "@UserWallet",
Description: "Wallet top-up",
},
},
)response = blnk.transactions.create({
"amount": 200,
"precision": 100,
"reference": "INV-2025-0008912",
"currency": "GBP",
"source": "@Funding",
"destination": "@UserWallet",
"description": "Wallet top-up",
})ApiResponse<JsonNode> response = blnk.transactions().create(
CreateTransactions.create()
.amount(200)
.precision(100)
.reference("INV-2025-0008912")
.currency("GBP")
.source("@Funding")
.destination("@UserWallet")
.description("Wallet top-up"));Your hook endpoint should log or persist the delivery. If nothing arrives, confirm the hook is active and the URL is reachable from Blnk Core.
Retries and timeouts
When your hook endpoint is unreachable, times out, or returns a non-2xx response, Blnk retries the delivery with backoff-up to the retry_count you set when registering the hook.
timeout sets how long Blnk waits for your endpoint on each attempt.
retry_count sets the maximum additional attempts after a failed delivery.
A delivery that failed while your receiver was down can arrive minutes later once the endpoint recovers.