Resources
Content Integration Webhooks

Content Integration Webhooks

Register webhooks, verify signatures, handle events, and fetch Content Integration results.

Use webhooks after the polling flow works. Webhooks notify your system when a Content Integration job finishes or fails.

Events

EventTrigger
content.completedA job reached completed.
content.failedA job reached failed.

Create webhook

Set the API base URL and create tutorflow-admin.cookies as described in Content API Key Management. Then discover the organization available to your admin session:

export TUTORFLOW_API_BASE_URL="https://api.tutorflow.io"
 
export TUTORFLOW_CONTENT_ORG_ID="$(curl -s "$TUTORFLOW_API_BASE_URL/v1/content/organizations" \
  -b tutorflow-admin.cookies | jq -r '.[0].id')"
WEBHOOK_RESPONSE="$(curl -sS -X POST "$TUTORFLOW_API_BASE_URL/v1/content/organizations/$TUTORFLOW_CONTENT_ORG_ID/webhooks" \
  -H "Content-Type: application/json" \
  -b tutorflow-admin.cookies \
  -d '{
    "url": "https://example.com/tutorflow/content-webhooks",
    "events": ["content.completed", "content.failed"]
  }')"
 
printf '%s\n' "$WEBHOOK_RESPONSE" | jq

Response:

{
  "id": "2f083d42-5c0f-4de6-9c3f-35c4823a589e",
  "organizationId": "00000000-0000-4000-8000-000000000001",
  "url": "https://example.com/tutorflow/content-webhooks",
  "events": ["content.completed", "content.failed"],
  "status": "ACTIVE",
  "secret": "generated-signing-secret"
}

The secret is shown once. Store it in your webhook receiver.

Set local variables from the response:

export TUTORFLOW_CONTENT_WEBHOOK_ID="$(printf '%s' "$WEBHOOK_RESPONSE" | jq -r '.id')"
export TUTORFLOW_CONTENT_WEBHOOK_SECRET="$(printf '%s' "$WEBHOOK_RESPONSE" | jq -r '.secret')"

If TUTORFLOW_CONTENT_WEBHOOK_ID is empty or null, inspect WEBHOOK_RESPONSE before continuing.

Request headers

X-Content-Integration-Event: content.completed
X-Content-Integration-Signature: hmac_sha256_signature
Content-Type: application/json

Receiver behavior

  1. Verify the signature.
  2. Return a 2xx response quickly.
  3. Use the job id in the payload to fetch the result endpoint.
  4. Treat repeated deliveries as duplicates and handle them idempotently.

Node.js signature verification

import crypto from 'node:crypto'
 
function verifyContentSignature(rawBody, signature, secret) {
  const expected = crypto
    .createHmac('sha256', secret)
    .update(rawBody)
    .digest('hex')
 
  return crypto.timingSafeEqual(
    Buffer.from(signature),
    Buffer.from(expected),
  )
}

List webhooks

curl "$TUTORFLOW_API_BASE_URL/v1/content/organizations/$TUTORFLOW_CONTENT_ORG_ID/webhooks" \
  -b tutorflow-admin.cookies

List responses do not include the signing secret.

Delete webhook

curl -X DELETE "$TUTORFLOW_API_BASE_URL/v1/content/organizations/$TUTORFLOW_CONTENT_ORG_ID/webhooks/$TUTORFLOW_CONTENT_WEBHOOK_ID" \
  -b tutorflow-admin.cookies

Retry handling

TutorFlow retries failed deliveries. Your receiver should:

  • Return 2xx only after accepting the event.
  • Avoid side effects before signature verification.
  • Store processed event ids or job ids to prevent duplicate processing.
  • Fetch the result endpoint if a webhook payload is not enough for your workflow.