Events
| Event | Fires when | data |
|---|---|---|
post.published | A post reached at least one platform, including a partial where one of three accounts failed. | postId, workspaceId, status (posted or partial), sandbox, targets[] with each platform, its outcome and the platform post id. |
post.failed | Every target failed. The post is terminal and nothing is retrying. | The same shape, with status "failed" and the platform’s own reason on each target. |
content.ready | A render finished and the video exists: a Turbo deck or any of the 15 Studio formats. | contentId, workspaceId, outputUrl, thumbnailUrl, durationMs, and postId when a post was waiting on it. |
credits.low | A spend took the balance under a fifth of the plan’s monthly credit allowance. Fires on the crossing, once. | balance, threshold, plan, creditsPerMonth. |
Subscribe
The response holds the signing secret. Store it; it is used to verify every delivery.
Request
curl -X POST "https://looptake.com/api/v1/webhooks" \
-H "Authorization: Bearer $LOOPTAKE_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"url": "https://your-app.example/hooks/looptake",
"events": ["post.published", "post.failed"]
}'Verify
Every delivery carries these headers:
Looptake-Signature: t=<unix seconds>,v1=<hex HMAC-SHA-256 over "<t>.<raw body>">, keyed with your signing secret.Looptake-Event: The event name, so a receiver can route without parsing the body.Looptake-Delivery: The delivery id. Stable across every attempt at one event, so deduplicate on it.
Compute the HMAC over the raw body bytes, not a re-serialised object. Reject timestamps older than 300 seconds.
Python
import hashlib, hmac, time
TOLERANCE = 300
def verify(raw_body: bytes, header: str, secret: str) -> bool:
parts = dict(p.split("=", 1) for p in header.split(",") if "=" in p)
t, v1 = parts.get("t"), parts.get("v1")
if not t or not v1 or abs(time.time() - int(t)) > TOLERANCE:
return False
expected = hmac.new(secret.encode(), f"{t}.".encode() + raw_body, hashlib.sha256).hexdigest()
return hmac.compare_digest(expected, v1)Node
import { createHmac, timingSafeEqual } from 'node:crypto';
const TOLERANCE = 300;
export function verify(rawBody, header, secret) {
const { t, v1 } = Object.fromEntries(header.split(',').map((p) => p.split('=', 2)));
if (!t || !v1 || Math.abs(Date.now() / 1000 - Number(t)) > TOLERANCE) return false;
const expected = createHmac('sha256', secret).update(`${t}.`).update(rawBody).digest();
const received = Buffer.from(v1, 'hex');
return expected.length === received.length && timingSafeEqual(expected, received);
}Delivery
At least once: deduplicate on Looptake-Delivery. Up to 6 attempts, 10 seconds each. After 10 undelivered events in a row the endpoint is disabled. GET /webhooks shows the last 5 deliveries per endpoint.
| Your response | Result |
|---|---|
| 2xx | Delivered |
| 429 | Retried |
| 5xx | Retried |
| No response | Retried |
| Other 4xx | Terminal |
| 3xx | Terminal |