Mempool Webhooks
Transactions on blockchains like Bitcoin take time to achieve finality — the point at which a transaction becomes permanent and irreversible (see Block confirmations and Finalisation for finalisation block values per chain). Waiting for the first confirmation, let alone final confirmation, can mean a noticeable delay before you know a deposit is on its way. Mempool webhooks close that gap: Liminal watches the blockchain's mempool — the pool of transactions broadcast to the network but not yet mined into a block — and sends you a webhook the moment it sees a transaction touching one of your wallets, before it has even been confirmed once.
As an Exchange or custodian, you can use mempool webhooks to notify your customers that their deposit has been seen by the network, well ahead of the first block confirmation.
Do not credit deposits based on mempool webhooksA transaction seen in the mempool has not been confirmed on-chain and can still be dropped, replaced, or reversed. Mempool webhooks are an early notification only — treat them as "we've seen this transaction," not "this transaction has happened." Crediting funds to a customer account, releasing goods, or otherwise acting as if the deposit is final based on a mempool webhook exposes you to fund-loss risk if the transaction never confirms. Always wait for the periodic or final confirmation webhook before crediting funds — see What happens next.
How to use this feature
- If your customer is depositing funds you'll receive in a deposit wallet, use mempool webhooks to acknowledge that the deposit is on its way — before the usual confirmation-based webhooks arrive. Use this only to update the customer's UI state (e.g. "deposit detected, awaiting confirmation"), never to credit their balance.
- If you regularly receive funds into a hot, warm, or cold wallet from an external source, mempool webhooks give you the earliest possible signal that funds are inbound.
- Mempool webhooks are a supplement to, not a replacement for, your existing confirmed and periodic transaction webhooks. You will still receive the normal progression of webhooks (periodic confirmation updates, then the final completed webhook) as the transaction proceeds on-chain.
NoteMempool webhooks are currently available for Bitcoin (BTC) only, and only for incoming (receive) and internal transfers. Outbound transactions you send are not covered by mempool webhooks in this release.
Best-effort detectionMempool detection is best-effort, not guaranteed. Liminal's infrastructure is set up to capture the large majority of incoming transactions while they're in the mempool, but there is no guarantee that every transaction will be seen by the mempool on the blockchain nodes we connect to. If a mempool webhook doesn't arrive for a given transaction, you will still receive the normal periodic and completed webhooks once it confirms — mempool detection is an enhancement on top of the existing confirmation-based webhook flow, not a dependency of it.
Enabling mempool webhooks
To receive mempool webhooks, enable Notify on Mempool Detection for the relevant protocol:
- Log into Vaults.
- Go to the Dev section.
- Under Webhook, select Settings.
- Select the blockchain network (currently BTC) in the Protocol field.
- Toggle Notify on Mempool Detection on.
- Select Save.
This setting lives in the same Notifications for Receive Transactions panel as your minimum block confirmation and periodic webhook settings (see Periodic Webhooks) — mempool detection, minimum confirmation, and periodicity are all configured together per protocol.
Mempool webhooks are sent to the same webhook URL and signed with the same signing key as your other transaction webhooks — no separate webhook URL is needed.
Chain support
| Chain | Detected at | Supported |
|---|---|---|
| Bitcoin | Mempool (pre-first-confirmation) | ✅ |
| All other chains | — | Not yet supported |
Mempool webhook response
Because the transaction has not been mined yet, several fields that are normally populated once a block is known are always null on a mempool webhook: block, fee, feeBaseUnit, runningbalance, raw, blockConfirmation, and every field inside blockConfirmationData. inputs is always an empty array — sender information is not decoded for this event.
externaladdress is always the literal string "external" on a mempool webhook. Unlike a confirmed transaction webhook, which resolves and reports the real sending address, mempool webhooks do not decode the sender — do not rely on externaladdress here to identify who sent the funds.
The transactionStatus object identifies a mempool webhook: legacyStatus: 1, primaryStatus: "pending", secondaryStatus: "awaiting_confirmation". This is the same status model used across all Liminal transaction webhooks — see Periodic Webhooks for how transactionStatus evolves as a transaction moves through confirmation.
One webhook per wallet
If a single transaction has outputs touching more than one of your wallets, you receive a separate mempool webhook per wallet — each webhook is scoped to exactly one walletid.
Within a given wallet's webhook:
outputs[]lists every output in the transaction, and each carriesisMine: trueif that specific output's address belongs to this webhook's wallet (walletid).effectivechange(andeffectivechangeusd/effectiveChangeBaseUnit) is the sum of only this wallet'sisMine: trueoutputs — not the transaction total, and not any other wallet's outputs.
- Incoming deposit, matched across multiple outputs to the same wallet
Example: a transaction pays out to several addresses; two of those outputs belong to the same Liminal wallet (walletid in this webhook). effectivechange sums just those two matching outputs for this wallet.
Multiple outputs to the same walletA single transaction can pay multiple outputs to addresses belonging to the same wallet (for example, a batch of change outputs from the same sender). Each matching output appears as its own entry in
outputs[]withisMine: true, andeffectivechange/effectivechangeusd/effectiveChangeBaseUnitreflect the combined total across those outputs for this wallet only.
{
"decimals": 8,
"fee": null,
"coin": "BTC",
"txid": "b7f007308cbce9085475bd44515ca2424330ffa259973f80793632722b6556fe",
"type": "receive",
"block": null,
"walletid": 2000004013,
"timestamp": "2026-07-27T13:03:07.072Z",
"feeBaseUnit": null,
"effectivechange": "0.00003375",
"externaladdress": "external",
"effectivechangeusd": "2.19446",
"effectiveChangeBaseUnit": "3375",
"runningbalance": null,
"raw": null,
"wallet": {
"id": 2000004013,
"name": "Test Deposit Wallet",
"type": "mpc",
"config": "2of2",
"balance": "0.00006742",
"address": "3JwtHWiy8ceFdy1wdajyx84Uwz8mYksuAf",
"chain": "BTC",
"subtype": "deposit",
"coin": "BTC",
"orgid": 2000000106,
"parentchain": "UTXO",
"isArchived": false,
"balanceUSD": "4.38398550",
"orgWebhook": "https://example.com/liminal-webhook"
},
"tokenContractAddress": "",
"sequenceId": "",
"explorerLink": "https://www.blockchain.com/btc/tx/b7f007308cbce9085475bd44515ca2424330ffa259973f80793632722b6556fe",
"inputs": [],
"outputs": [
{
"address": "3EezqEv88dMu55ZDRvvpB96r24vVVUxEc3",
"amount": "0.00001687",
"amountBaseUnit": "1687",
"isMine": true,
"wallet": {
"id": 2000004013,
"name": "Test Deposit Wallet",
"type": "mpc",
"config": "2of2",
"status": 1,
"balance": "0.00006742",
"balanceusd": "4.38398550",
"raw": null,
"issynced": true,
"walletpath": "m/49/1/0/2000000106/0/0/0",
"walletidentifier": "3JwtHWiy8ceFdy1wdajyx84Uwz8mYksuAf",
"chain": "BTC",
"subtype": "deposit",
"coin": "BTC",
"orgid": 2000000106,
"parentchain": "UTXO",
"canInitiatorSign": 1,
"suspendedStatus": 0,
"suspendedRemark": null,
"isArchived": false,
"teamId": null,
"version": 2,
"updatedAt": "2026-07-27T13:00:01.000Z",
"account": null
}
},
{
"address": "3EezqEv88dMu55ZDRvvpB96r24vVVUxEc3",
"amount": "0.00001688",
"amountBaseUnit": "1688",
"isMine": true,
"wallet": {
"id": 2000004013,
"name": "Test Deposit Wallet",
"type": "mpc",
"config": "2of2",
"status": 1,
"balance": "0.00006742",
"balanceusd": "4.38398550",
"raw": null,
"issynced": true,
"walletpath": "m/49/1/0/2000000106/0/0/0",
"walletidentifier": "3JwtHWiy8ceFdy1wdajyx84Uwz8mYksuAf",
"chain": "BTC",
"subtype": "deposit",
"coin": "BTC",
"orgid": 2000000106,
"parentchain": "UTXO",
"canInitiatorSign": 1,
"suspendedStatus": 0,
"suspendedRemark": null,
"isArchived": false,
"teamId": null,
"version": 2,
"updatedAt": "2026-07-27T13:00:01.000Z",
"account": null
}
},
{
"address": "3EVoQjV69RrEpuG9UpkYrGmND5y8y8ZPGQ",
"amount": "0.00085682",
"amountBaseUnit": "85682",
"isMine": false
}
],
"blockConfirmation": null,
"transactionStatus": {
"legacyStatus": 1,
"primaryStatus": "pending",
"secondaryStatus": "awaiting_confirmation"
},
"blockConfirmationData": {
"periodicity": null,
"block": null,
"numberOfConfirmations": null
},
"txStatus": 2,
"id": 30,
"createdAt": "2026-07-27T13:03:07.275Z"
}- Internal transfer (sender and destination wallets belong to the same organisation)
type: "internal"When Liminal can match the transaction to a known outgoing request from a wallet in the same organisation as the destination wallet,
typeis set tointernalinstead ofreceive. This is common for internal sweeps or rebalancing between your own wallets.
{
"decimals": 8,
"fee": null,
"coin": "BTC",
"txid": "7b2d4f6a8c1e3b5d9f7a2c4e6b8d1f3a5c7e9b2d4f6a8c1e3b5d7f9a2c4e6b8d",
"type": "internal",
"block": null,
"walletid": 4102,
"timestamp": "2026-07-15T11:05:12.789Z",
"feeBaseUnit": null,
"effectivechange": "0.50000000",
"externaladdress": "external",
"effectivechangeusd": "33500.00000",
"effectiveChangeBaseUnit": "50000000",
"runningbalance": null,
"raw": null,
"wallet": {
"id": 4102,
"name": "BTC Cold Storage",
"type": "mpc",
"config": "3of5",
"balance": "12.50000000",
"address": "bc1qcoldstoragewalletaddress0000000000000000",
"chain": "BTC",
"subtype": "holding",
"coin": "BTC",
"orgid": 1290,
"parentchain": "UTXO",
"isArchived": false,
"balanceUSD": "837500.00000000",
"orgWebhook": "https://example.com/liminal-webhook"
},
"tokenContractAddress": "",
"sequenceId": "",
"explorerLink": "https://www.blockchain.com/btc/tx/7b2d4f6a8c1e3b5d9f7a2c4e6b8d1f3a5c7e9b2d4f6a8c1e3b5d7f9a2c4e6b8d",
"inputs": [],
"outputs": [
{
"address": "bc1qcoldstoragewalletaddress0000000000000000",
"amount": "0.50000000",
"amountBaseUnit": "50000000",
"isMine": true,
"wallet": {
"id": 4102,
"name": "BTC Cold Storage",
"type": "mpc",
"config": "3of5",
"status": 1,
"balance": "12.50000000",
"balanceusd": "837500.00000000",
"raw": null,
"issynced": true,
"walletpath": "m/49/1/0/1290/0/0/0",
"walletidentifier": "bc1qcoldstoragewalletaddress0000000000000000",
"chain": "BTC",
"subtype": "holding",
"coin": "BTC",
"orgid": 1290,
"parentchain": "UTXO",
"canInitiatorSign": 1,
"suspendedStatus": 0,
"suspendedRemark": null,
"isArchived": false,
"teamId": null,
"version": 1,
"updatedAt": "2026-07-10T08:00:00.000Z",
"account": null
}
}
],
"blockConfirmation": null,
"transactionStatus": {
"legacyStatus": 1,
"primaryStatus": "pending",
"secondaryStatus": "awaiting_confirmation"
},
"blockConfirmationData": {
"periodicity": null,
"block": null,
"numberOfConfirmations": null
},
"txStatus": 2,
"id": 9002,
"createdAt": "2026-07-15T11:05:13.012Z"
}What happens next
A mempool webhook does not replace any of your existing webhooks — it arrives in addition to, and before, the normal sequence. Each stage carries its own transactionStatus object:
- Mempool webhook — this page. Early notification only; the transaction is not yet on-chain and can still be dropped or replaced.
"transactionStatus": { "legacyStatus": 1, "primaryStatus": "pending", "secondaryStatus": "awaiting_confirmation" } - Periodic confirmation webhooks as the transaction accumulates block confirmations — see Periodic Webhooks.
"transactionStatus": { "legacyStatus": 2, "primaryStatus": "pending", "secondaryStatus": "awaiting_finalisation" } - Final completed webhook once the transaction reaches finality. This is the point at which it is safe to credit the deposit.
"transactionStatus": { "legacyStatus": 4, "primaryStatus": "success", "secondaryStatus": "completed" }
NoteMempool detection is best-effort (see above), so a mempool webhook may not arrive for every transaction even when the feature is enabled. This does not affect the rest of the flow — you will still receive the normal periodic and completed webhooks once the transaction confirms.
Field descriptions
legacyStatus: The legacy status of the transaction, as a number. 1 (Pending) throughout the mempool stage.
Example: 1
primaryStatus: The broader stage of the transaction.
Example: pending
secondaryStatus: The specific stage of the transaction. awaiting_confirmation identifies the mempool (pre-first-confirmation) stage — check this field to distinguish a mempool webhook from a periodic (awaiting_finalisation) or completed (completed) webhook.
Example: awaiting_confirmation
type: Direction of the transaction relative to your organisation. receive for an external deposit, internal when both the sending and receiving wallet belong to your organisation. Outbound (send) transactions are not covered by mempool webhooks.
Example: receive
walletid: The wallet this webhook is scoped to. If a transaction touches more than one of your wallets, each wallet receives its own webhook with its own walletid — see One webhook per wallet.
Example: 2000004013
effectivechange / effectiveChangeBaseUnit / effectivechangeusd: The net amount received by this webhook's wallet, summed across every output in the transaction that belongs to it (see One webhook per wallet). effectivechange is the decimal coin amount, effectiveChangeBaseUnit is the same amount in the chain's base unit (satoshis for BTC) as a string, and effectivechangeusd is the USD-converted value at time of detection.
Example: "0.00003375" / "3375" / "2.19446"
externaladdress: Always the literal string "external" on a mempool webhook — the sending address is not decoded at this stage. A confirmed transaction webhook reports the real sending address instead.
Example: "external"
outputs[].isMine: true when this specific output's address belongs to the wallet this webhook is scoped to (walletid), false otherwise. Only outputs with isMine: true are counted in effectivechange.
Example: true
outputs[].wallet: Present only when isMine: true. Nested wallet metadata for the matching output — note this nested object uses different field names than the top-level wallet object (e.g. balanceusd instead of balanceUSD, walletidentifier instead of address).
