docs / resources / webhooks
Webhooks
Instead of polling, let SongAPI POST the result to you. When a generation reaches a terminal state we send a generation.complete or generation.failed event to:
- the
webhook_urlyou passed on that request, if any, and - every enabled endpoint registered on your dashboard that subscribes to the event type.
## Event payload
{ "id": "evt_k8PqXw4RtY7NcAbD", "type": "generation.complete", "created": 1784692061, "livemode": true, "data": { "generation": { … full generation object, incl. clips … } } }
data.generation is the same generation object that GET /v1/generations/:id returns. livemode is false for sandbox generations — sandbox webhooks fire like live ones, so you can test end to end for free.
## Headers & signature
Every delivery is signed with your account's webhook secret (whsec_…, on the dashboard):
content-type: application/json user-agent: SongAPI-Webhooks/1.0 songapi-signature: t=1784692061,v1=5257a869e7…
v1 is the hex HMAC-SHA256 of "{t}.{raw body}" keyed with your secret. Verify before trusting the payload:
import { createHmac, timingSafeEqual } from "node:crypto"; function verify(rawBody, header, secret) { const { t, v1 } = Object.fromEntries( header.split(",").map((p) => p.split("=")) ); const expected = createHmac("sha256", secret) .update(`${t}.${rawBody}`) .digest("hex"); return timingSafeEqual(Buffer.from(v1), Buffer.from(expected)); }
Reject stale timestamps (e.g. older than 5 minutes) to guard against replays. Compute the HMAC over the raw request body, before any JSON parsing.
## Delivery behavior
- Endpoints must be HTTPS URLs and respond within 5 seconds; redirects are not followed.
- Respond with any 2xx quickly and do your processing async.
- Webhooks are a convenience, not the source of truth: if a delivery fails, the generation is unaffected — you can always fetch it via
GET /v1/generations/:id. Delivery results for registered endpoints are visible on the dashboard. - You can fire a test event to any registered endpoint from the dashboard.