Webhooks

Webhooks let external systems react to CmdCal events without polling the jobs API. When a subscribed event fires, CmdCal sends an HTTP POST to your configured endpoint with a JSON payload describing the event.

Outbound render-event webhooks are available on Pro and Business plans. Free-tier organizations should poll `GET /api/v2/jobs/:id` for job status updates. The webhook configuration UI is coming soon -- contact support to enable webhooks for your organization in the meantime.

Planned Events

EventTrigger
render.completedA render job reaches succeeded status
render.failedA render job reaches failed status
approval.changedA job's approval status changes to approved or rejected

Expected Payload Format

JSON
{
  "event": "render.completed",
  "timestamp": "2026-03-26T14:30:00Z",
  "data": {
    "job_id": "job_abc123",
    "job_url": "/api/v2/jobs/job_abc123",
    "status": "succeeded",
    "document_title": "Q1 Board Deck",
    "slide_count": 8,
    "deck_score": 92,
    "approval_status": "needs_review",
    "download_url": "https://...",
    "brand_pack_id": "bp_xyz789"
  }
}

Retry Behavior

When your endpoint returns a non-2xx status code or times out (30-second window), CmdCal retries delivery with exponential backoff:

  • Retry 1: after 30 seconds
  • Retry 2: after 2 minutes
  • Retry 3: after 10 minutes
  • Retry 4: after 1 hour

After 4 failed retries, the event is marked as undeliverable. Failed deliveries are visible in the webhook logs.

Security

Each webhook request includes an X-CmdCal-Signature header containing an HMAC-SHA256 signature of the request body. Verify it against the webhook secret provided during configuration:

TYPESCRIPT
import { createHmac } from "crypto";

function verifyWebhookSignature(
  body: string,
  signature: string,
  secret: string,
): boolean {
  const expected = createHmac("sha256", secret)
    .update(body)
    .digest("hex");
  return signature === expected;
}

Polling as a Fallback

Until webhooks are configured for your organization, poll the job status endpoint:

Terminal
# Poll every 2 seconds until the job completes
curl https://api.paperjsx.com/api/v2/jobs/JOB_ID \
  -H "Authorization: Bearer pj_live_YOUR_KEY"

The job record's status field transitions to succeeded or failed when processing finishes.

Next Steps

  • Render Jobs -- understand job states that trigger webhook events
  • Automation -- use webhooks in CI/CD pipelines
  • Billing -- webhooks are included on Pro and Business plans