Webhooks
Webhooks let your backend update orders when an on-chain payment changes state. Manatee sends the same signed event envelope for BTC, BCH, and XMR to the webhook_url provided at payment creation.
Event types
| Event type | Meaning | Typical action |
|---|---|---|
payment.detected | A matching transaction was seen, but confirmations are below the required threshold. | Show "payment seen" or "waiting for confirmations". |
payment.confirmed | The transaction reached required_confirmations. | Mark the order as paid or ready for fulfillment. |
payment.expired | A still-pending payment reached expires_at without a qualifying transaction. | Stop accepting payment instructions and let the customer create a new payment. |
Treat payment.detected as an early signal. Use payment.confirmed as the fulfillment trigger for most production flows.
Payload example
{
"version": "1",
"type": "payment.confirmed",
"data": {
"payment_id": "pay_abc123",
"txid": "a1b2c3d4...",
"asset": "bch",
"network": "testnet",
"address": "bchtest:qr95sy3j9xwd2ap32xkykttr4cvcu7as4ytjg7p7mc",
"amount_sats": 10000,
"received_sats": 10000,
"amount_atomic": "10000",
"received_atomic": "10000",
"atomic_unit": "satoshi",
"confirmations": 1,
"required_confirmations": 1,
"webhook_url": "https://yourapp.example/webhooks/bch"
}
}
| Data field | Meaning |
|---|---|
payment_id | Manatee payment ID stored next to your order ID |
asset | btc, bch, or xmr |
network | mainnet, testnet, stagenet, or regtest (depending on the asset) |
txid | Matching on-chain transaction ID; empty for an unpaid payment.expired event |
address | Canonical address for the asset and network; BCH uses prefixed CashAddr |
amount_sats | Expected amount in the asset's satoshis |
received_sats | Matching native-asset amount observed so far |
amount_atomic / received_atomic | Exact decimal-string amounts in the unit named by atomic_unit; present for every asset |
atomic_unit | satoshi for BTC/BCH or piconero for XMR |
confirmations | Confirmation count at event creation |
required_confirmations | Confirmation threshold configured for the payment |
webhook_url | Delivery URL stored with the payment |
Store the payment ID returned during payment creation next to your own order ID. The webhook intentionally does not include your reference; use the stored mapping or retrieve the payment when that field is needed.
For XMR, use the decimal strings exactly as received: 1000000000000
piconero is 1.000000000000 XMR. XMR events use the same timestamped HMAC
headers and verification procedure as BTC/BCH. The
XMR Stagenet quickstart includes an end-to-end
synthetic webhook check before a real Stagenet payment.
Required headers
| Header | Purpose |
|---|---|
X-Manatee-Event-Id | Unique event delivery ID for replay protection and idempotency. |
X-Manatee-Event-Type | Event type, such as payment.detected, payment.confirmed, or payment.expired. |
X-Manatee-Timestamp | Unix timestamp in seconds used in signature verification. |
X-Manatee-Signature | HMAC-SHA256 signature over timestamp + "." + raw request body. |
Always verify X-Manatee-Signature before updating an order, and reject timestamps outside your allowed clock-skew window. See Security for implementation details.
For backwards compatibility, Manatee currently also sends the legacy X-Event-ID, X-Event-Type, and X-Signature headers. New integrations should use the X-Manatee-* headers.
Send a test webhook
Before creating a payment, you can send a synthetic payment.confirmed event to your webhook endpoint:
curl -X POST "$MANATEE_API_URL/v1/webhooks/test" \
-H "Authorization: Bearer $MANATEE_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"webhook_url": "https://yourapp.example/webhooks/bch",
"asset": "bch"
}'
This checks endpoint reachability, payload parsing, signature handling, and response behavior without sending an on-chain transaction. Use btc, bch, or xmr; omitting asset keeps the backwards-compatible BTC default. The payload network is inherited from the authenticated API key. The request does not create a payment and is not stored in payment history.
Webhook failed
If webhook delivery fails:
- Confirm the URL is publicly reachable by server-to-server HTTP requests.
- Return a
2xxresponse only after your app accepts the event. - Bypass browser challenges, captchas, or login redirects only for the webhook route.
- Verify the raw request body, not parsed JSON, when checking signatures.
- Store
X-Manatee-Event-Idvalues so repeated delivery is safe.
For retry behavior and more details, see Webhook Delivery in a Crypto Payment API.