Concepts
Webhooks
Events delivered to your endpoints, their signatures, retries and the event log.
On this page
Webhooks tell your system when something happens: a release is approved, goes live, gets its codes; a payout is paid. Register https endpoints in Workspace › API or with /webhooks.
Deliveries#
Each delivery is a POST with a JSON body:
{
"id": "…",
"event": "release.live",
"occurredAt": "2026-11-01T09:30:00.000Z",
"organizationId": "…",
"livemode": true,
"data": { "releaseId": "…", "release": { "id": "…", "title": "Night Signals", "status": "LIVE" } }
}Release events carry the release as GET /releases/{id} shows it. Answer with any 2xx within eight seconds; do slow work after answering.
A failed delivery is tried twice more, about a minute and then about fifteen minutes later; the endpoint's log in the API tab shows each try. GET /events keeps every event for 30 days, so a system that was down can catch up.
Signatures#
Check the X-Stillmore-Signature header before trusting a delivery. It reads t=<unix seconds>,v1=<hex>: an HMAC-SHA256, with the endpoint's secret, of the timestamp, a dot and the raw body (<t>.<raw body>). While a secret is rotated, two v1 signatures come; either one matching is enough.
import { verifyWebhook } from "./stillmore-api";
const rawBody = await request.text();
const ok = await verifyWebhook(rawBody, request.headers.get("x-stillmore-signature"), process.env.WEBHOOK_SECRET!);
if (!ok) return new Response("bad signature", { status: 400 });Events#
| Event | When |
|---|---|
release.submitted | A release was submitted for review |
release.changes_requested | Review asked for changes |
release.rejected | A release was rejected in review |
release.approved | A release was approved and is ready to go to the stores |
release.sent | A release was sent to the stores |
release.codes_assigned | A release's UPC or ISRCs were assigned or changed |
release.store_links_updated | A release's store links were added or changed |
release.live | A release went live in the stores |
release.provider_rejected | The delivery partner rejected a release |
release.taken_down | A release was taken down |
takedown.requested | A takedown was requested for a live release |
payout.requested | A payout was requested |
payout.approved | A payout was approved |
payout.rejected | A payout was rejected |
payout.paid | A payout was paid |
payout.failed | A payout could not be completed |
statement.published | A royalty statement was published |
claim.created | A copyright claim was filed |
claim.status_changed | A copyright claim changed status |