Skip to main content

Webhooks — API Reference

ContentsPal can send HTTP POST requests to an external URL when job-related events occur. This allows integrating with external systems (ERPs, CRMs, etc.) in near real-time.

Written by Pal

Configuration

Organization admins configure webhooks under Organization → Settings → Integrations:

  • Endpoint URL — HTTPS URL that will receive POST requests.

  • Enabled — Toggle to enable/disable delivery without removing the URL.

  • Signing Secret (optional) — Shared secret for HMAC-SHA256 payload signature verification.

The webhook config is stored on the organization's settings.webhook field.

Events

Event

Trigger

job.created

A new job is created in the organization

job.updated

A job's name, identifier, status, customer, or insurance details are modified

job.deleted

A job is deleted

report.generated

A report finishes generating and becomes available to download

webhook.test

Sent on demand when an admin uses Send Test in the webhook settings

job.* events fire only for jobs linked to the organization (via sharing.organizationId); report.generated fires for the organization's reports. The event type is also sent in the X-Webhook-Event header.

HTTP Request

Method: POST

Headers:

Header

Description

Content-Type

application/json

User-Agent

ContentsPal-Webhook/1.0

X-Webhook-Event

Event type (e.g. job.created)

X-Webhook-Signature

sha256={hex} — HMAC-SHA256 of the request body using the signing secret. Only present when a signing secret is configured.

Timeout: 30 seconds. Requests that take longer are aborted.

Payload Schema

Every delivery shares the same envelope — event, timestamp, organizationId — plus one event-specific object: a job object for the job.* events and webhook.test, or a report object for report.generated.

job.created / job.updated / job.deleted

{  "event": "job.created",  "timestamp": "2026-03-03T14:30:00.000Z",  "organizationId": "abc123",  "job": {    "id": "job-uuid",    "name": "Smith Residence - Fire Damage",    "identifier": "1201-260001",    "status": "inventory",    "customer": {      "name": "John Smith",      "email": "[email protected]",      "phone": "555-1234"    },    "insurance": {      "claimNumber": "CLM-2026-001",      "company": "State Farm",      "lossType": "fire",      "policyNumber": "POL-123",      "agent": "Jane Doe",      "phone": "555-5678",      "email": "[email protected]"    },    "ownerUserId": "user-uuid",    "createdAt": "2026-03-01T10:00:00.000Z",    "updatedAt": "2026-03-03T14:30:00.000Z"  },  "changes": [    {      "field": "settings.customer",      "oldValue": { "name": "J Smith" },      "newValue": { "name": "John Smith", "email": "[email protected]", "phone": "555-1234" }    }  ]}

Field Details

Field

Type

Description

event

string

One of job.created, job.updated, job.deleted

timestamp

string

ISO-8601 timestamp of when the event was dispatched

organizationId

string

Organization UUID

job.id

string

Job UUID

job.name

string

Job name

job.identifier

string?

Scheme-generated job identifier, when the organization has a job identifier scheme configured. Populated for jobs created in the app and via the API (REST, inbound webhook, and AI/MCP).

job.status

string?

Job status. For organizations with a custom status list this is the custom value (e.g. new); otherwise a built-in status (e.g. inventory, packout, completed).

job.customer

object?

Customer details (name, email, phone)

job.insurance

object?

Insurance details (claimNumber, company, lossType, etc.)

job.ownerUserId

string

UUID of the user who owns the job

job.createdAt

string

Job creation timestamp

job.updatedAt

string

Job last-update timestamp

changes

array?

Only present for job.updated. Lists which watched fields changed with old/new values.

report.generated

Sent once when a report transitions into a downloadable state (status completed or partial with a download URL).

{  "event": "report.generated",  "timestamp": "2026-03-03T14:30:00.000Z",  "organizationId": "abc123",  "report": {    "id": "report-uuid",    "reportType": "inventory",    "title": "Inventory Report — Smith Residence",    "status": "completed",    "fileType": "pdf",    "downloadUrl": "https://storage.googleapis.com/.../report.pdf",    "jobId": "job-uuid",    "jobName": "Smith Residence - Fire Damage",    "requestingUserId": "user-uuid",    "createdAt": "2026-03-03T14:29:00.000Z",    "completedAt": "2026-03-03T14:30:00.000Z"  }}

Field Details

Field

Type

Description

report.id

string

Report UUID

report.reportType

string

Report type (e.g. inventory, valuation, custody)

report.title

string?

Report title, when set

report.status

string

completed or partial

report.fileType

string?

File type of the download (e.g. pdf, zip)

report.downloadUrl

string

Signed URL to download the report file

report.jobId

string?

UUID of the job the report belongs to, when applicable

report.jobName

string?

Name of the job the report belongs to, when applicable

report.requestingUserId

string?

UUID of the user who requested the report

report.createdAt

string

When the report job was created

report.completedAt

string

When the report became downloadable

webhook.test

Sent on demand from the webhook settings (Send Test) to verify connectivity and signature handling. It uses the same envelope as a job.* event, with a job object containing placeholder values (id test-…, identifier TEST-260001, status active).

Watched Fields (for job.updated)

  • jobName — The job's display name

  • jobIdentifier — The scheme-generated job identifier

  • status — The job's status (built-in or custom status value)

  • settings.customer — Customer contact details (name, email, phone)

  • settings.insurance — Insurance claim details (claimNumber, company, lossType, policyNumber, agent, etc.)

Verifying Signatures

If a signing secret is configured, verify the X-Webhook-Signature header:

const crypto = require('crypto');function verifySignature(body, secret, signatureHeader) {  if (typeof signatureHeader !== 'string') {    return false;  }  const expected = 'sha256=' + crypto    .createHmac('sha256', secret)    .update(body, 'utf8')    .digest('hex');  const expectedBuffer = Buffer.from(expected, 'utf8');  const receivedBuffer = Buffer.from(signatureHeader, 'utf8');  if (expectedBuffer.length !== receivedBuffer.length) {    return false;  }  return crypto.timingSafeEqual(expectedBuffer, receivedBuffer);}

Error Handling

  • Webhooks are fire-and-forget. Failed deliveries (non-2xx responses, timeouts, network errors) are logged server-side but not retried.

  • Your endpoint should return a 2xx status code to acknowledge receipt.

  • Delivery is best-effort: events may occasionally be missed during outages or function cold starts.

Did this answer your question?