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 namejobIdentifier— The scheme-generated job identifierstatus— 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.