資料
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-Delivery-Id: 00000000-0000-4000-8000-000000000003
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. Store X-Content-Integration-Delivery-Id and the job id so repeated deliveries are idempotent.

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 delivery ids or job ids to prevent duplicate processing.
  • Fetch the result endpoint if a webhook payload is not enough for your workflow.