Use case
Be told the moment a clip is ready
Have Clipped POST to your server when a clip is found, when one is posted, and when a streamer goes live or offline.
Instead of asking every few seconds, give Clipped a URL and it calls you. Events arrive in order, at least once, each signed with a secret only you and Clipped hold.
-
1Make the webhook
The URL must be public https.
eventsandchannelsnarrow what is sent; leave them out for everything. The answer carries the webhook'ssecret, once: keep it.A webhook starts from the moment it is made. Nothing from before is sent.
Shell curl -X POST https://app.clipped.so/api/v1/webhooks -H "Authorization: Bearer $KEY" \ -H "Content-Type: application/json" \ -d '{ "url": "https://example.com/clipped", "events": ["clip.ready", "clip.published"] }' -
2Check the signature, then answer fast
Clipped-Signatureist=<unix seconds>,v1=<hex>: HMAC-SHA256 of<t>.<raw body>with the secret. Check it against the raw body, before parsing, and refuse atmore than five minutes old.Answer 2xx within ten seconds and do the work afterwards. Anything else is retried, from 15 seconds up to an hour apart, and later events wait behind it so order is kept.
A receiver (Node)JavaScript import { createHmac, timingSafeEqual } from "node:crypto" import { createServer } from "node:http" function verify(rawBody, header, secret) { const parts = Object.fromEntries(header.split(",").map((p) => p.split("="))) const t = Number(parts.t) if (!t || Math.abs(Date.now() / 1000 - t) > 300) return false const expected = createHmac("sha256", secret).update(`${t}.${rawBody}`).digest("hex") return expected.length === parts.v1?.length && timingSafeEqual(Buffer.from(expected), Buffer.from(parts.v1)) } createServer(async (req, res) => { let raw = "" for await (const chunk of req) raw += chunk if (!verify(raw, req.headers["clipped-signature"] ?? "", process.env.CLIPPED_WEBHOOK_SECRET)) { res.writeHead(401).end() return } res.writeHead(204).end() // answer first const event = JSON.parse(raw) if (event.type === "clip.ready") console.log("new clip", event.data.id, event.data.score, event.data.thumbnail) if (event.type === "clip.published") console.log("posted", event.data.clip.id, "to", event.data.destination, event.data.url) }).listen(8080) -
3Send yourself a test
A
pingto the webhook now, and the answer says how your URL responded.GET /api/v1/webhooksshows each webhook's last delivery, which is where to look when events stop.Shell curl -X POST https://app.clipped.so/api/v1/webhooks/$WEBHOOK_ID/test -H "Authorization: Bearer $KEY"
Worth knowing
clip.readyis sent once, when the clip is found, which is often before its thumbnail exists. Clipped tries to make one first; when it can't in a few secondsthumbnailisnull, andGET /api/v1/clips/{id}/thumbnailhas it later.- Every event has an
id. Seeing one twice means a retry of the same event: ignore the second. - Twenty failures in a row switch a webhook off.
PATCH { "active": true }switches it back on from where it stopped. - An account can have five webhooks.