Skip to the main content

Webhooks

Signed events sent to a URL you own. Delivery is at least once.

Events

EventFires whendata
post.publishedA 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.failedEvery 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.readyA 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.lowA 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 responseResult
2xxDelivered
429Retried
5xxRetried
No responseRetried
Other 4xxTerminal
3xxTerminal

Describe it. Looptake writes, renders, scores and posts it to TikTok, Reels and Shorts.

Free trial, no card. Paid plans from $29/month.