Webhooks· Get told first

Your server, told first.

A signed POST to your URL when a chain event you named lands. Retried while your server is down, rolled back when a fork orphans the block.

$10 per 1M events · plus your stack, $5/mo minimum

27 triggers
amm-swaps→ my-app.com/webhookSTATUSLATENCYTRIGGERBLOCK20084 mscontract_call9,137,00220091 msft_transfer9,137,004503retryingcontract_call9,137,00720077 mscontract_call · retried9,137,007signed · ed25519example logdedupe on (tx_id, event_index, block_hash)

Signed in every format

Every delivery carries an ed25519 signature, whatever body shape you pick. Check it with one published public key.

Forks undo themselves

A reorg sends one rollback listing everything that may have reached you, so your state follows the chain back.

Failures you can read

The last 100 attempts, with a diagnosis. Failed events wait for you to resend them, so nothing is silently dropped.

Create one

One command, no subgraph to deploy

Name the events you care about and the URL to send them to. It starts at the chain tip, and the signing secret prints once.

~/my-app
$secondlayer webhooks create amm-swaps \
--url https://my-app.com/webhook \
--trigger '{"type":"contract_call",
"contractId":"SP….amm","functionName":"swap-*"}' \
--trigger '{"type":"sbtc_deposit"}'
triggers 2 validated
starts at chain tip
secret whsec_… (shown once, store it now)
✓ amm-swaps live
$secondlayer webhooks test amm-swaps --post
✓ 200 · chain.test.apply · 63 ms
Check it's us

Two lines before you trust a body

The default format carries a per-webhook HMAC. Every format also carries our ed25519 signature over the delivery id and raw body, so a payload from anyone else fails both.

receiver.tsts
1import { verifyWebhookSignature, decodeChainWebhook }
2 from "@secondlayer/sdk";
3
4// raw body first, before any JSON parsing
5if (!verifyWebhookSignature(rawBody, req.headers, secret))
6 return new Response("bad signature", { status: 401 });
7
8const delivery = decodeChainWebhook(rawBody);
9switch (delivery.data.trigger) {
10 case "contract_call": // fully typed
11}

4bytes as received

5ours, or it is a 401

8typed from here

Reorgs

When a fork orphans a block, you hear about it

The rollback names the fork point and lists every event that may have arrived, including ones whose response never made it back. Undo each entry and your state matches the chain again.

POST /webhookjson
1{
2 "type": "chain.reorg.rollback",
3 "data": {
4 "action": "rollback",
5 "fork_point_height": 9137006,
6 "orphaned": [
7 { "tx_id": "0x9f2c…", "event": { "event_index": 3, … } }
8 ],
9 "truncated": false
10 }
11}

5undo everything after this

Volume meter$10 / 1M events

Counted when an event is delivered. Your webhooks run on your hosted stack, which bills memory plus storage. Webhooks only: $5/mo minimum while your stack runs. Pause or delete every webhook and it stops within minutes.

Never billedRetries

Nor the rows we read to match your triggers. A flaky receiver never raises the bill.

Triggers

Every event Stacks emits, and the protocols on top

Each trigger accepts only the fields that make sense for it, so a typo is a 400 at create time instead of a webhook that never fires.

STX

live
stx_transferstx_mintstx_burnstx_lock

Filter by sender, recipient, or an amount range.

Tokens

live
ft_transferft_mintft_burnnft_transfernft_mintnft_burn

Scope to one asset, or to every SIP-010 or SIP-009 token at once.

Contracts

live
contract_callcontract_deployprint_event

Match function names with swap-*, or every print from one contract by topic.

sBTC

live
sbtc_depositsbtc_withdrawal_create…_accept…_reject…_swept_confirmed

Swept-confirmed fires once, when the sweep reaches 6 Bitcoin confirmations.

Runes

rolling out
rune_etchrune_mintrune_transferrune_burn

By rune id or name, on Bitcoin blocks from 840,000.

PoX-5

live
print_event · pox-5

Stacking prints by topic, such as register-for-bond, as they land.

When it goes wrong

Your server will go down. Here is what happens next.

Retried, then held

Failed deliveries retry with backoff, up to 7 times hosted. After that the circuit opens so one dead endpoint can't block the queue, and the events wait for you.

secondlayer webhooks dead amm-swaps secondlayer webhooks requeue amm-swaps <id>

Diagnosed in plain numbers

Five fixed rules read your last 100 attempts: rate-limited, down, rejecting, slow, or lag on our side. Each one shows the evidence it fired on.

secondlayer webhooks doctor amm-swaps

Replayed on purpose

Re-deliver up to 100,000 historical blocks over the same webhook. Replays are flagged, idempotent, and never move the live cursor.

secondlayer webhooks replay amm-swaps \ --from-block 9100000 --to-block 9137000
Formats

Arrives in the shape your stack already reads

The same event data in every format, under the key that format expects. Pick one per webhook.

standard-webhooksrawcloudeventsinngesttriggercloudflare

Run it yourself

Hosted webhooks run on api.secondlayer.tools, one isolated stack per account. On your own box the same CLI, SDK and delivery semantics apply, with your limits instead of ours, and Runes triggers already work there.

Webhooks questions

Are deliveries ordered?
No. Delivery is at-least-once and unordered. Key your state on (tx_id, event_index, block_hash) and don't assume arrival order matches chain order.
What happens on a reorg?
You get one chain.reorg.rollback listing every event that may have reached you from the orphaned blocks. Undo each one. Anything above the finalized height, six Bitcoin confirmations back, is provisional.
Can a new webhook backfill history?
A new webhook starts at the chain tip. To send history, replay a range of up to 100,000 blocks over it. For bulk history, read the Index instead.
Do I pay for retries?
No. You pay $10 per 1M events delivered, plus your hosted stack while it runs. Retries, and the rows we read to match your triggers, are never billed.
How do I know a delivery came from you?
Every delivery carries an ed25519 signature over webhook-id.rawBody, checkable against our published public key. The default format adds a per-webhook HMAC on top.

Have your server told first today