Core concepts

Webhooks

Signed event deliveries: registration, the event catalog, signature verification, and retries.

Registering

Register HTTPS endpoints in the Developers hub or via the API (up to 10 per account). The signing secret is returned once, on creation; store it immediately.

curl
curl https://www.opencharts.com/api/v1/webhooks \
  -H "Authorization: Bearer $OPENCHARTS_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Production events",
    "url": "https://example.com/hooks/opencharts",
    "events": ["video.completed", "video.failed", "song.completed", "research.completed"]
  }'

The event catalog

The catalog below is generated from the same registry the API validates subscriptions against, so these names can never drift. Two naming notes: the code job type (job ids code_…) emits code_build.completed / code_build.failed, and Theo Agent runs emit evi.run_completed / evi.run_failed. Subscribing to an unknown or upcoming (not-yet-emitted) event returns a 400 naming it.

Events
project.created
projects
Sent when a new project is created.
project.updated
projects
Sent when a project is updated.
project.shared
projects
Sent when a project is shared with a user.
project.deleted
projects
Sent when a project is deleted.
comment.created
collaboration (upcoming)
Sent when a new comment is created.
team.member_added
teams
Sent when a user is added to an organization.
team.member_removed
teams
Sent when a user is removed from an organization.
export.completed
exports
Sent when a file export has completed.
template.published
templates (upcoming)
Sent when a template is published to the community.
meeting.completed
meetings
Sent when a meeting recording has been transcribed and processed.
code_canvas.iteration_complete
code_canvas
Sent when a background Code Canvas iteration finishes successfully.
code_canvas.iteration_failed
code_canvas
Sent when a background Code Canvas iteration fails or times out.
code_canvas.published
code_canvas
Sent when a Code Canvas project is published to a public URL.
podcast.completed
podcasts
Sent when a Podcast Creator Canvas episode finishes generating.
podcast.failed
podcasts
Sent when a Podcast Creator Canvas episode fails after retries.
notes_movie.completed
projects
Sent when a Notes Movie Mode render finishes and the assembled video is ready.
notes_movie.failed
projects
Sent when a Notes Movie Mode render fails.
skill.installed
skills
Sent when the user installs a skill from the skill store.
evi.run_completed
flows
Sent when an editor-driven Flow run finishes successfully.
evi.run_failed
flows
Sent when an editor-driven Flow run fails.
video.completed
api_jobs
Sent when an API video generation job finishes and the clip is ready.
video.failed
api_jobs
Sent when an API video generation job fails.
song.completed
api_jobs
Sent when an API song generation job finishes and the audio versions are ready.
song.failed
api_jobs
Sent when an API song generation job fails.
code_build.completed
api_jobs
Sent when an API Code Canvas build job finishes and the project is ready.
code_build.failed
api_jobs
Sent when an API Code Canvas build job fails.
research.completed
api_jobs
Sent when an API deep-research job finishes and the report is ready.
research.failed
api_jobs
Sent when an API deep-research job fails.

The delivery payload

POST body
{
  "eventId": "0d5b2c9e-...",
  "eventVersion": "2026-02-24",
  "event": "video.completed",
  "occurredAt": "2026-07-22T14:03:11.000Z",
  "timestamp": "2026-07-22T14:03:11.000Z",
  "data": {
    "jobId": "vid_6870f3...",
    "status": "complete",
    "pollUrl": "https://www.opencharts.com/api/v1/jobs/vid_6870f3..."
  }
}

For job events, data always carries jobId, status, and pollUrl; pipelines append extras such as an asset URL, a project id, or an error message on failures.

Request headers
X-OpenCharts-Event
string
The event name.
X-OpenCharts-Event-Id
uuid
Unique per delivery; use it to deduplicate.
X-OpenCharts-Event-Version
date string
Payload schema version.
X-OpenCharts-Signature
sha256=<hex>
HMAC-SHA256 of the raw request body, keyed with your webhook secret.

Verifying signatures

Always verify before trusting

Compute the HMAC over the raw request body (before any JSON parsing) and compare with a constant-time check.

import crypto from "node:crypto";

export function verifyOpenChartsSignature(rawBody, signatureHeader, secret) {
  // Header format: "sha256=<hex>"
  const expected =
    "sha256=" +
    crypto.createHmac("sha256", secret).update(rawBody, "utf8").digest("hex");
  return (
    signatureHeader?.length === expected.length &&
    crypto.timingSafeEqual(Buffer.from(signatureHeader), Buffer.from(expected))
  );
}

// Express example: use the RAW body, not the parsed JSON.
app.post("/hooks/opencharts", express.raw({ type: "*/*" }), (req, res) => {
  const ok = verifyOpenChartsSignature(
    req.body.toString("utf8"),
    req.header("X-OpenCharts-Signature"),
    process.env.OPENCHARTS_WEBHOOK_SECRET,
  );
  if (!ok) return res.status(401).end();
  const payload = JSON.parse(req.body.toString("utf8"));
  // handle payload.event / payload.data ...
  res.status(200).end();
});

Retries & health

Each delivery attempt times out after 5 seconds and retries twice (after 1s, then 3s) on failure. Any 2xx from your endpoint counts as delivered; respond fast and do heavy work asynchronously. After 5 consecutive failed deliveries a webhook is automatically disabled (re-enable it with PATCH /webhooks/{id} once your endpoint recovers).

Inspecting deliveries

Every attempt is logged with status, duration, and response info: