Developers · Integrations

Put the interpretation everywhere your service already goes.

A caption overlay for your livestream, signed webhooks for your systems, and a small REST API. Included on every plan, including the free trial.

01

Caption overlay

A transparent web page that shows live captions in one language. It joins your room like a listener, captions only — no audio, no buttons, nothing to click. It hides itself whenever the service is not live.

https://acts2.prebabel.ai/overlay/HK7CMN?lang=cmn&style=lower-third&bg=transparent&size=2&lines=2

Copy your ready-made URL from Account › Integrations (or the host app’s Settings › Integrations).

ParameterValuesDefault
langAny room language, e.g. cmn, yue, en, or orig (the speaker’s own words)cmn
stylelower-third (max 2 lines, centred) · full (caption screen)lower-third
bgtransparent · blacktransparent
size1 · 2 · 32
lines1–2 for lower-third, 1–3 for full2
scripthans (简) · hant (繁) — Chinese onlyas sent

OBS Studio

  1. Sources › + › Browser.
  2. Paste the overlay URL. Width 1920, height 1080.
  3. Leave “Custom CSS” empty; tick “Refresh browser when scene becomes active”.
  4. Place the source above your camera. The background stays transparent.

ProPresenter

  1. Add a Web Fill to a Props or Stage layer (ProPresenter 7.9+), or Media › Web Page.
  2. Paste the overlay URL and set the size to your output (1920 × 1080).
  3. Use bg=black with style=full for a confidence monitor or an overflow-room screen.

Also works with vMix (Web Browser input) and Ecamm Live (Overlay › Web).

02

Webhooks

Get told when a service starts, pauses, resumes and ends — and receive the full transcript after it ends. Up to 3 https endpoints per organisation, set by an admin in Account › Integrations.

Eventdata
service.started{service_id, source, targets, started_at}
service.paused{service_id, at}
service.resumed{service_id, at}
service.ended{service_id, started_at, ended_at, billable_seconds, peak_listeners, end_reason}
transcript.ready{service_id, languages: {yue: "…", cmn: "…"}} — final captions joined, sent after the service ends

Every event is a JSON body {id, type, created, org_id, data}, sent as a POST:

Example
POST /your/endpoint
content-type: application/json
Acts2-Event-Id: evt_01J9Z6Q4T8
Acts2-Signature: t=1760436000,v1=5f2b…c9e1

{
  "id": "evt_01J9Z6Q4T8",
  "type": "service.ended",
  "created": 1760436000,
  "org_id": "7c1e…",
  "data": {
    "service_id": "b3f0…",
    "started_at": "2026-10-12T02:00:11Z",
    "ended_at": "2026-10-12T03:31:40Z",
    "billable_seconds": 3180,
    "peak_listeners": 128,
    "end_reason": "host"
  }
}
  • Headers: Acts2-Signature: t=<unix>,v1=<hex HMAC-SHA256(secret, "<t>.<raw body>")>, Acts2-Event-Id, content-type: application/json.
  • Any 2xx counts as delivered. 10 s timeout. Retries with backoff: 1 min, 5 min, 30 min, 2 h, 12 h.
  • Endpoints must be public https — localhost and private IPs are refused.
  • Transcripts are kept only to build transcript.ready, then deleted (at the latest after 7 days).
  • Events can arrive more than once: dedupe on id.

Verify the signature

Compute the HMAC over the raw body before parsing it, compare in constant time, and reject old timestamps.

Node.js
import crypto from "node:crypto";

// rawBody: the exact request body string (verify before JSON.parse)
export function verifyActs2(rawBody, signatureHeader, secret, toleranceSec = 300) {
  const parts = Object.fromEntries(
    String(signatureHeader).split(",").map((kv) => kv.trim().split("=", 2)),
  );
  const t = Number(parts.t);
  if (!Number.isFinite(t) || Math.abs(Date.now() / 1000 - t) > toleranceSec) return false;
  const expected = crypto.createHmac("sha256", secret).update(`${t}.${rawBody}`).digest("hex");
  const a = Buffer.from(expected, "utf8");
  const b = Buffer.from(String(parts.v1 ?? ""), "utf8");
  return a.length === b.length && crypto.timingSafeEqual(a, b);
}

// Express: app.post("/acts2", express.raw({ type: "application/json" }), (req, res) => {
//   if (!verifyActs2(req.body.toString("utf8"), req.get("Acts2-Signature"), process.env.ACTS2_WEBHOOK_SECRET))
//     return res.sendStatus(400);
//   const event = JSON.parse(req.body);   // dedupe on event.id (Acts2-Event-Id)
//   res.sendStatus(204);
// });
Python
import hashlib, hmac, time

def verify_acts2(raw_body: bytes, signature_header: str, secret: str, tolerance: int = 300) -> bool:
    try:
        parts = dict(kv.strip().split("=", 1) for kv in signature_header.split(","))
        t = int(parts["t"])
    except (KeyError, ValueError):
        return False
    if abs(time.time() - t) > tolerance:
        return False
    signed = f"{t}.".encode() + raw_body
    expected = hmac.new(secret.encode(), signed, hashlib.sha256).hexdigest()
    return hmac.compare_digest(expected, parts.get("v1", ""))

# Flask:
# @app.post("/acts2")
# def acts2():
#     if not verify_acts2(request.get_data(), request.headers.get("Acts2-Signature", ""), SECRET):
#         abort(400)
#     event = request.get_json()   # dedupe on event["id"]
#     return "", 204
03

REST API

Read your organisation, service history and live room status from your own tools.

Authenticate with an org API key: Authorization: Bearer acts2_sk_… Admins create and revoke keys in Account › Integrations; a key is shown once. Rate limit: 60 requests per minute per key.

GET /api/v1/org

Org name, room code, listener URL, plan, allowance

curl https://acts2.prebabel.ai/api/v1/org \
  -H "Authorization: Bearer $ACTS2_KEY"

GET /api/v1/services?limit=&before=

Service history, newest first

curl "https://acts2.prebabel.ai/api/v1/services?limit=10" \
  -H "Authorization: Bearer $ACTS2_KEY"

GET /api/v1/services/:id

One service

curl https://acts2.prebabel.ai/api/v1/services/SERVICE_ID \
  -H "Authorization: Bearer $ACTS2_KEY"

GET /api/v1/rooms/current

Live status, listeners by language

curl https://acts2.prebabel.ai/api/v1/rooms/current \
  -H "Authorization: Bearer $ACTS2_KEY"

Errors use {"error": {"code", "message"}} with a matching HTTP status (401 bad key, 404 not found, 429 rate limited).

Questions about an integration? Email support — we read every message.