Listen for Stacks contract events
A contract announces what happened by printing an event. A webhook posts each print to a URL you run, as the block lands. One key, one call:
$ curl -X POST " $SECONDLAYER_API_URL /api/webhooks " \
-H " Authorization: Bearer sk-sl_… " \
-H " Content-Type: application/json " \
-d ' {
"name": "sbtc-withdrawals",
"url": "https://my-app.com/webhooks/sbtc",
"triggers": [{
"type": "print_event",
"contractId": "SM3VDXK3WZZSA84XXFKAFAF15NNZX32CTSG82JFQ4.sbtc-registry",
"topic": "withdrawal-create"
}]
} '
import { SecondLayer , trigger } from " @secondlayer/sdk ";
const sl = new SecondLayer ({ apiKey : process . env . SECONDLAYER_API_KEY });
const { webhook , signingSecret } = await sl . webhooks . create ({
name : " sbtc-withdrawals ",
url : " https://my-app.com/webhooks/sbtc ",
triggers : [
trigger . printEvent ({
contractId : " SM3VDXK3WZZSA84XXFKAFAF15NNZX32CTSG82JFQ4.sbtc-registry ",
topic : " withdrawal-create ",
}),
],
});
201 response.json json Copy
{
" webhook ": {
" id ": " 65ea1ed2-7b2d-4255-ae82-347fbc1023b3 ",
" name ": " sbtc-withdrawals ",
" status ": " active ",
" kind ": " chain ",
" format ": " standard-webhooks ",
" url ": " https://my-app.com/webhooks/sbtc ",
▸ "lastDeliveryAt": null, … "circuitFailures": 0, 4 lines " lastDeliveryAt ": null ,
" lastSuccessAt ": null ,
" circuitOpenedAt ": null ,
" circuitFailures ": 0 ,
" createdAt ": " 2026-10-28T17:59:19.072Z ",
" triggers ": [
{
" type ": " print_event ",
" topic ": " withdrawal-create ",
" contractId ": " SM3VDXK3WZZSA84XXFKAFAF15NNZX32CTSG82JFQ4.sbtc-registry "
}
],
▸ "maxRetries": 7, … "warning": null 6 lines " maxRetries ": 7 ,
" timeoutMs ": 10000 ,
" concurrency ": 4 ,
" lastError ": null ,
" receiverCertExpiresAt ": null ,
" warning ": null
},
" signingSecret " : " … "
}
The signing secret comes back once, so store it. Every delivery carries a signature you check with it: Verify the signature .
The trigger above matches one print topic. Match other prints, or other events:
To match Use Other keys in the print where (Print fields )Calls instead of prints trigger.contractCall (Triggers )
Each delivery is one signed POST. The print arrives as data.event.data.value, already parsed from Clarity: Event shapes .
A webhook starts at the chain tip and never backfills. To re-deliver a block range over it, replay . To read past prints once and resume from a block, page Streams with a cursor.
Hosted webhooks run on your stack, $5 a month at the minimum (What it costs ). Triggers and the create call: Create a webhook .
← Previous MCP and skills Next → FT transfers by address Edit on GitHub