Skip to main content

Quick Start

This guide walks you through a complete test payment: create a payment request, send Bitcoin to the returned address, and receive signed webhook events as the payment is detected and confirmed.

If you are new to Bitcoin payments, think of Manatee as the monitoring and notification layer between your checkout and your Bitcoin wallet. Your customer sends Bitcoin directly to an address from your wallet. Manatee watches that address and tells your backend when the payment was seen and when it has enough confirmations.

What you need

  • A Manatee account and API key from Beta Access
  • A receiving wallet for the network you want to test
  • A public webhook URL, for example from webhook.site, ngrok, Cloudflare Tunnel, or your own staging server
  • curl or an API client such as Postman

Manatee API keys are network-scoped. Use your testnet key for testnet payments and your mainnet key only when you are ready to send real Bitcoin. API keys are server-side credentials for payment API requests; dashboard access uses your separate account login.

NetworkAddress examplesBitcoin valueTypical use
Testnettb1..., m..., n..., 2...No real valueIntegration tests, webhook tests, checkout QA
Mainnetbc1..., 1..., 3...Real BTCProduction payments

Bitcoin terms used in this guide

TermPlain-English meaning
Satoshi / satsThe smallest Bitcoin unit. 10000 sats is 0.00010000 BTC.
AddressThe destination your customer pays. Testnet and mainnet addresses are not interchangeable.
TestnetA Bitcoin test network with coins that have no real value. Use it before touching real BTC.
ConfirmationA Bitcoin block that includes the transaction, or a block added after it. More confirmations mean lower reversal risk.
WebhookAn HTTP request Manatee sends to your backend when a payment changes status.
Receive descriptor / extended public keyRead-only wallet data that lets Manatee derive fresh receiving addresses without being able to spend funds. This may appear as xpub, ypub, or zpub on mainnet and as tpub, upub, or vpub on testnet/Testnet4.

What Manatee does and does not do

Manatee monitors Bitcoin addresses, tracks payment status, and sends signed webhook events to your backend. It does not hold customer funds, provide a wallet, convert fiat currencies, or send refunds. Your business keeps control of the receiving wallet and decides when an order is safe to fulfill.

If your checkout prices products in EUR, USD, or another fiat currency, calculate the Bitcoin amount in your own application before creating the payment. Send that amount to Manatee as amount_sats and use expires_in to limit how long the quote is valid.

1. Prepare a wallet

For production, use a dedicated business wallet or account. For testing, create a separate testnet wallet so test funds and real funds never mix.

Testnet wallet

Sparrow Wallet is a good option for manual testing:

  1. Install Sparrow Wallet.
  2. Restart Sparrow in testnet mode via Tools -> Restart in Testnet4 Mode.
  3. Create a new wallet.
  4. For a manual-address test, open the Receive tab and copy a testnet address.

Manatee reports test payments as testnet in the API response, even if your wallet UI calls the network Testnet4. If you export account-level public wallet data from Sparrow in Testnet4 mode, Sparrow may show prefixes such as tpub or vpub instead of the mainnet-style xpub, ypub, or zpub. That is expected: Manatee uses xPub as a generic term for extended public keys, and the backend supports the testnet prefixes when it builds the receive descriptor.

You can request Testnet4 coins from public faucets such as:

Faucets are third-party services and their availability or balance can change. Try another listed faucet if one is temporarily empty or unavailable.

Mainnet wallet

For mainnet, use an address from the wallet where your business should receive Bitcoin. Native SegWit addresses (bc1q...) are recommended for lower fees.

Do not send mainnet BTC to a testnet address, and do not send testnet BTC to a mainnet address. Wallets usually prevent this, but it is worth checking before your first production payment.

2. Choose your receive mode

Your account needs one receive mode before a payment can be created.

ModeHow it worksBest for
Descriptor / extended public keyManatee derives a fresh receiving address for each payment.Production checkouts, accounting, better privacy
destination_addressYour backend sends the receiving address when creating the payment.Simple tests, manual flows, custom wallet routing

For a production checkout, prefer a descriptor-backed setup or an extended public key such as zpub/vpub. It gives every order a fresh address and makes reconciliation easier. If your beta key is not configured for the receive mode you want, configure it in the dashboard, contact Manatee support, or reply to your API-key email before testing.

If you are using destination_address, your API key must allow customer-provided addresses. Otherwise payment creation will fail because Manatee cannot choose a safe receiving address on your behalf.

3. Set environment variables

Use the hosted API base URL unless you received a separate staging URL.

export MANATEE_API_URL="https://api.manatee-api.io"
export MANATEE_API_KEY="YOUR_TESTNET_OR_MAINNET_KEY"
export WEBHOOK_URL="https://your-public-url.example/webhooks/btc"

For a first test, WEBHOOK_URL can be a webhook.site URL. For signature verification tests, use a small endpoint you control so you can read the raw request body.

Your webhook URL must be reachable by server-to-server HTTP requests. If you use Cloudflare, a WAF, bot protection, captchas, browser challenges, or login redirects, bypass those protections for the webhook path and rely on Manatee's X-Signature HMAC header to verify authenticity.

Optional: 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": "'"$WEBHOOK_URL"'"
}'

This checks endpoint reachability, headers, payload parsing, and signature verification without sending a Bitcoin transaction. It does not create a payment and is not stored in payment history.

4. Create a payment

If your API key is descriptor-backed or extended-public-key-backed, omit destination_address. Manatee will derive the receiving address and return it in the response.

curl -sS -X POST "$MANATEE_API_URL/v1/btc/payments" \
-H "Authorization: Bearer $MANATEE_API_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: order-10042-create-payment" \
-d '{
"amount_sats": 10000,
"underpayment_tolerance_ppm": 1000,
"webhook_url": "'"$WEBHOOK_URL"'",
"required_confirmations": 1,
"expires_in": 3600,
"reference": "order-10042"
}'

If your key is configured to allow customer-provided addresses, send a network-matching destination_address. Manatee rejects private/local webhook URLs in production and also rejects a destination_address that already has another active payment:

curl -sS -X POST "$MANATEE_API_URL/v1/btc/payments" \
-H "Authorization: Bearer $MANATEE_API_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: order-10042-create-payment" \
-d '{
"amount_sats": 10000,
"underpayment_tolerance_ppm": 1000,
"destination_address": "tb1q...",
"webhook_url": "'"$WEBHOOK_URL"'",
"required_confirmations": 1,
"expires_in": 3600,
"reference": "order-10042"
}'

Example response:

{
"id": "pay_abc123",
"address": "tb1q...",
"amount_sats": 10000,
"underpayment_tolerance_ppm": 1000,
"status": "pending",
"confirmations": 0,
"required_confirmations": 1,
"network": "testnet",
"webhook_url": "https://your-public-url.example/webhooks/btc",
"reference": "order-10042",
"created_at": "2026-05-07T10:00:00Z",
"expires_at": "2026-05-07T11:00:00Z"
}

Save the id and send amount_sats to address. If underpayment_tolerance_ppm is set, Manatee accepts payments down to the configured tolerance; for example, 1000 allows a 0.1% underpayment.

For checkout integrations, store your own order ID in reference and save the returned Manatee id next to that order in your database. Webhooks contain the Manatee payment_id, so your backend should use that stored mapping to find the order. You can also fetch the payment by ID to read its reference.

In a customer-facing checkout, show the returned address, the exact Bitcoin amount, and the expiry time. Most wallets can scan a QR code, so you may want to render the address and amount as a Bitcoin payment QR code in your own frontend.

5. Pay the address

For testnet, send testnet coins from your test wallet to the returned address. For mainnet, send real BTC only after you have completed a testnet run.

The payment starts as pending. After Manatee detects an incoming transaction it may move to detected; once it reaches required_confirmations, it becomes confirmed.

For many digital-product test flows, required_confirmations: 1 is enough. For production, choose the threshold based on the value and risk of the order. Higher thresholds are safer but slower.

6. Check payment status

curl -sS "$MANATEE_API_URL/v1/btc/payments/pay_abc123" \
-H "Authorization: Bearer $MANATEE_API_KEY"

Statuses you should handle:

StatusMeaning
pendingWaiting for a matching transaction
detectedTransaction was seen, but confirmations are still below the threshold
confirmedPayment reached the configured confirmation threshold
expiredPayment expired before it was paid
cancelledPayment was cancelled before detection

Only pending payments expire automatically. If a transaction was already detected before expires_at, Manatee keeps tracking it instead of turning it into an expired payment.

7. Receive the webhook

Manatee sends webhook events to your webhook_url when the transaction is first detected and again when it reaches the required confirmation threshold.

Use payment.confirmed as the event that marks an order as paid. Treat payment.detected as an early signal that a transaction exists but is not final enough for most fulfillment flows.

Webhook payloads include the Manatee payment_id, but they do not include your reference. Store the payment_id returned when you create the payment so your webhook handler can map the event back to your order.

{
"version": "1",
"type": "payment.confirmed",
"data": {
"payment_id": "pay_abc123",
"txid": "a1b2c3d4...",
"address": "tb1q...",
"amount_sats": 10000,
"received_sats": 10000,
"confirmations": 1,
"webhook_url": "https://your-public-url.example/webhooks/btc"
}
}

Webhook requests include these headers:

HeaderPurpose
X-Event-IDUnique event delivery ID
X-Event-TypeEvent type, for example payment.detected or payment.confirmed
X-SignatureHMAC-SHA256 signature of the raw request body

Return any 2xx response after your application has accepted the event.

Webhook deliveries can be repeated. Store processed events by X-Event-ID, and make marking an order as paid idempotent.

Timing and retries

payment.detected is triggered from Bitcoin Core ZMQ when the transaction appears in the mempool. The internal webhook queue is checked about every 500 ms, so successful webhook deliveries usually arrive within a few seconds.

If your endpoint returns a non-2xx response or cannot be reached, delivery is retried after 1 minute, 5 minutes, 30 minutes, 2 hours, 6 hours, and then every 24 hours until the maximum of 10 attempts is reached.

Pending payment expiry is checked every 30 seconds.

8. Verify webhook signatures

Always verify the signature before marking an order as paid. The signature is calculated over the raw request body, before JSON parsing.

const crypto = require('crypto');

function verifySignature(rawBody, signatureHeader, secret) {
if (!signatureHeader?.startsWith('sha256=')) {
return false;
}

const actual = Buffer.from(signatureHeader.slice('sha256='.length), 'hex');
const expected = crypto
.createHmac('sha256', secret)
.update(rawBody)
.digest();

if (actual.length !== expected.length) {
return false;
}

return crypto.timingSafeEqual(actual, expected);
}

Use the webhook signing secret shown for the API key in the dashboard. Keep it separate from your API key, store it only on your server, and rotate it if it is exposed.

If you use Express or another framework that parses JSON automatically, make sure you keep access to the raw request body for this check. Verifying the parsed JSON object will produce a different signature.

Mainnet checklist

Before switching from testnet to mainnet:

  • Use your mainnet API key.
  • Use a mainnet wallet address or mainnet receive descriptor/extended public key.
  • Keep required_confirmations at the level your business requires.
  • Use a production webhook endpoint with signature verification enabled.
  • Store the Manatee payment.id, your reference, the returned address, and the final txid for reconciliation.
  • Never reuse testnet addresses, testnet keys, or faucet funds in a mainnet flow.

Useful next steps

  • Review the API Reference for all request and response fields.
  • Add retries around payment creation with a stable Idempotency-Key.
  • Store webhook events idempotently by X-Event-ID so repeated deliveries cannot double-credit an order.