Webhooks - Expected Payloads

Discovery Webhooks — Expected Return Payloads

Overview

This documentation describes the webhook payload structure and events available from the Discovery application. All webhooks deliver HTTP POST requests to configured endpoints with a Content-Type: application/json header.

Your endpoint must return a 2xx response or Discovery will retry delivery.


Verifying Webhook Authenticity

Every webhook request includes a Signature header containing an HMAC-SHA256 hash of the raw request body, signed with your tenant's webhook secret. You can verify the request came from Discovery by recomputing the hash and comparing it to the header value.


Payload Envelope

Every webhook payload uses the following top-level structure:

  • type — Name of the triggered event (e.g., WorkOrderWasCreated)
  • data — Event-specific payload object

json { "type": "WorkOrderWasCreated", "data": { "id": "...", ... } }

> Note: There is no event or occurred_at field at the top level. The event name is always under the key type.


Event Categories

Gathered Vendor Invoice File Events

Two events track raw invoice file activity in the Gather - Vendor Invoices section:

  1. GatheredVendorInvoiceFileWasCreated — Fires when a file is uploaded
  2. GatheredVendorInvoiceFileWasUpdated — Fires when a gathered file is modified

Both payloads include a document_url field — a temporary signed URL providing direct access to the file: - On WasCreated: valid for 1 week - On WasUpdated: valid for 4 hours

Consumers should download the file promptly after receiving the webhook, as these URLs expire.

Example — GatheredVendorInvoiceFileWasCreated:

Additional internal fields are omitted for brevity.

json { "type": "GatheredVendorInvoiceFileWasCreated", "data": { "id": "gvif-a1b2c3d4-e5f6-7890-abcd-ef1234567890", "tenant_id": "ten-11112222-3333-4444-5555-666677778888", "status": "Gathered", "source": "Email", "batch_name": "April 2026 Invoices", "document_name": "invoice-april-2026.pdf", "document_mime": "application/pdf", "document_size": 245678, "document_md5": "d41d8cd98f00b204e9800998ecf8427e", "document_path": "gathered-vendor-invoice-files/2026-04-15/gvif-a1b2c3d4.pdf", "document_url": "https://app.discoveryapp.io/api/gathered-vendor-invoice-files/gvif-a1b2c3d4.../pdf?expires=...&signature=...", "extracted_at": null, "uploaded_by_user_id": "usr-aaaabbbb-cccc-dddd-eeee-ffff00001111", "bulk_import_id": null, "archived_at": null, "archived_by_user_id": null, "deleted_at": null, "created_at": "2026-04-15T10:30:00+00:00", "updated_at": "2026-04-15T10:30:00+00:00" } }


Incident Events

Four incident lifecycle events are available:

  1. IncidentWasCreated — Initial incident logged
  2. IncidentWasUpdated — Field modifications applied
  3. IncidentWasEscalated — Status changed to Escalated
  4. IncidentWasClosed — Status changed to Closed

All four use the same payload shape. See the Incident Fields Reference at the bottom of this article for field descriptions.

Example — IncidentWasCreated:

Additional internal fields are omitted for brevity.

json { "type": "IncidentWasCreated", "data": { "id": "inc-a1b2c3d4-e5f6-7890-abcd-ef1234567890", "tenant_id": "ten-11112222-3333-4444-5555-666677778888", "incident_number": 42, "display_id": "000042", "status": "New", "priority": "Low", "source": "Web", "type_id": 3, "reason_id": 7, "location_id": "loc-a1b2c3d4-e5f6-7890-abcd-ef1234567890", "service_id": null, "assigned_to_id": null, "description": "Container was missed during scheduled pickup.", "filed_by": { "first_name": "Jane", "last_name": "Smith", "email": "[email protected]", "type": "Employee" }, "money_saved": { "amount": "0", "currency": "USD" }, "minutes_saved": 0, "reported_at": "2026-04-15T10:30:00+00:00", "occurred_at": null, "created_at": "2026-04-15T10:30:00+00:00", "updated_at": "2026-04-15T10:30:00+00:00" } }


OCR Vendor Invoice Events

Three events track invoice extraction in the Extract - OCR Vendor Invoices section:

  1. OCRVendorInvoiceWasCreated — Extraction job initiated
  2. OCRVendorInvoiceWasExtracted — Parsing complete
  3. OCRVendorInvoiceWasUpdated — Manual corrections applied

All three use the same payload shape. Note that line items are a related object and are not included in the webhook payload.

Example — OCRVendorInvoiceWasCreated:

Additional internal fields are omitted for brevity.

json { "type": "OCRVendorInvoiceWasCreated", "data": { "id": "ocr-a1b2c3d4-e5f6-7890-abcd-ef1234567890", "tenant_id": "ten-11112222-3333-4444-5555-666677778888", "gathered_vendor_invoice_file_id": "gvif-a1b2c3d4-e5f6-7890-abcd-ef1234567890", "status": "Queued", "vendor_name": "Acme Waste Solutions", "invoice_number": "INV-2026-0042", "account_number": "ACCT-123456", "invoice_date": "2026-04-01", "invoice_total": { "amount": "25000", "currency": "USD" }, "subtotal": { "amount": "25000", "currency": "USD" }, "amount_due": { "amount": "25000", "currency": "USD" }, "total_tax": { "amount": "0", "currency": "USD" }, "remit_to_address": "PO Box 1000, Chicago, IL 60601", "service_address": "456 Client Ave, Milwaukee, WI 53202", "duplicate_ocr_vendor_invoice_id": null, "archived_at": null, "created_at": "2026-04-15T10:30:00+00:00", "updated_at": "2026-04-15T10:30:00+00:00" } }


Vendor Contract Events

VendorContractStatusChanged fires whenever a contract transitions between statuses.

In addition to the full vendor contract entity attributes, the payload includes the following extra fields:

| Field | Type | Description | |---|---|---| | old_status | string \| null | The previous status (null when the contract is first created) | | new_status | string | The status the contract transitioned to | | triggered_by | string \| null | The user ID of the employee who made the change, or null if system-triggered | | status_change_timestamp | ISO 8601 string | When the status change occurred |

Valid status values: Pre Term, Initial Term, Initial Term (Can Cancel Auto Renewal), Initial Term (Auto Renewal Cancelled), Standard Renewal Term, Standard Renewal Term (Can Cancel Auto Renewal), Standard Renewal Term (Auto Renewal Cancelled), Rolling Month To Month, Pending Termination, Terminated

Example — VendorContractStatusChanged:

Additional internal fields are omitted for brevity.

json { "type": "VendorContractStatusChanged", "data": { "id": "vc-a1b2c3d4-e5f6-7890-abcd-ef1234567890", "tenant_id": "ten-11112222-3333-4444-5555-666677778888", "vendor_id": "vnd-a1b2c3d4-e5f6-7890-abcd-ef1234567890", "contract_number": 12345678, "display_id": "12345678", "status": "Initial Term", "effective_date": "2024-01-01", "expiration_date": "2027-01-01", "term_months": 36, "auto_renewal_term_months": 12, "old_status": "Pre Term", "new_status": "Initial Term", "triggered_by": "usr-aaaabbbb-cccc-dddd-eeee-ffff00001111", "status_change_timestamp": "2026-04-15T10:30:00+00:00", "created_at": "2024-01-01T09:00:00+00:00", "updated_at": "2026-04-15T10:30:00+00:00" } }


Vendor Invoice Events

Four complete lifecycle events in the Audit - Vendor Invoices section:

  1. VendorInvoiceWasCreated — Initial invoice record created
  2. VendorInvoiceWasUpdated — Invoice details modified
  3. VendorInvoiceWasApproved — Invoice approved for payment
  4. VendorInvoiceWasPosted — Invoice posted to the accounting system

All four use the same payload shape. Note that line items are a related object and are not included in the webhook payload.

Example — VendorInvoiceWasCreated:

Additional internal fields are omitted for brevity.

json { "type": "VendorInvoiceWasCreated", "data": { "id": "vi-a1b2c3d4-e5f6-7890-abcd-ef1234567890", "tenant_id": "ten-11112222-3333-4444-5555-666677778888", "ocr_vendor_invoice_id": "ocr-a1b2c3d4-e5f6-7890-abcd-ef1234567890", "vendor_account_id": "va-a1b2c3d4-e5f6-7890-abcd-ef1234567890", "status": "Matched", "invoice_number": "INV-2026-0042", "invoice_date": "2026-04-01", "fiscal_period": "2026-04-01", "total_current_charges": { "amount": "25000", "currency": "USD" }, "adjusted_total_current_charges": { "amount": "25000", "currency": "USD" }, "balance": { "amount": "0", "currency": "USD" }, "approved_at": null, "posted_at": null, "auto_approved": false, "archived_at": null, "created_at": "2026-04-15T10:30:00+00:00", "updated_at": "2026-04-15T10:30:00+00:00" } }


Work Order Events

Five work order lifecycle events are available:

  1. WorkOrderWasCreated — Work order created
  2. WorkOrderWasUpdated — Work order details modified
  3. WorkOrderWasCancelled — Work order cancelled
  4. WorkOrderWasCompleted — Work order marked as complete
  5. WorkOrderStatusWasChanged — Status transition occurred

Events 1–4 all use the same payload shape. WorkOrderStatusWasChanged includes all the same fields plus five additional ones shown below.

Example — WorkOrderWasCreated:

Additional internal fields are omitted for brevity.

json { "type": "WorkOrderWasCreated", "data": { "id": "wo-a1b2c3d4-e5f6-7890-abcd-ef1234567890", "tenant_id": "ten-11112222-3333-4444-5555-666677778888", "service_id": "svc-a1b2c3d4-e5f6-7890-abcd-ef1234567890", "vendor_id": "vnd-a1b2c3d4-e5f6-7890-abcd-ef1234567890", "source": "Phone", "requested_action": "Extra Pickup", "status": 9, "decorated_status": "New", "display_id": "00012345", "order_number": 12345, "requested_completion_date": "2026-04-22", "on_site_contact_name": "Jane Smith", "on_site_contact_number": "555-867-5309", "is_complete": false, "is_over_due": false, "incident_id": null, "deleted_at": null, "created_at": "2026-04-15T10:30:00+00:00", "updated_at": "2026-04-15T10:30:00+00:00" } }

Example — WorkOrderStatusWasChanged:

Includes all work order fields above, plus the following additional fields. Additional internal fields are omitted for brevity.

json { "type": "WorkOrderStatusWasChanged", "data": { "id": "wo-a1b2c3d4-e5f6-7890-abcd-ef1234567890", "...all work order fields...", "old_status": 9, "old_status_name": "New", "new_status": 1, "new_status_name": "Requested", "status_change_timestamp": "2026-04-15T10:31:00+00:00" } }


Incident Fields Reference

| Field | Type | Description | |---|---|---| | id | UUID | Unique identifier | | tenant_id | UUID | Your tenant ID | | incident_number | integer | Display number | | display_id | string | Zero-padded display ID (e.g., 000042) | | status | string | New, Needs Additional Information, Awaiting Execution, In Progress, On Hold, Escalated, Closed | | priority | string | Low, Medium, High | | source | string | Email, Internal - Company, Mobile - Customer Service App, Phone, Sensor, Text Message, Vendor - Email, Vendor - Phone, Web, Web - Client Portal | | type_id | integer | ID of the incident type | | reason_id | integer | ID of the incident reason | | location_id | UUID | ID of the associated location | | service_id | UUID \| null | ID of the associated service, if any | | assigned_to_id | UUID \| null | ID of the assigned employee | | client_contact_id | UUID \| null | ID of the client contact | | closed_by_employee_id | UUID \| null | ID of the employee who closed the incident | | description | string \| null | Incident description | | hauler_case_number | string \| null | Hauler-assigned case number | | money_saved | object | Money saved, e.g. {"amount": "10000", "currency": "USD"} | | minutes_saved | integer | Minutes saved | | filed_by | object | User who filed the incident: first_name, last_name, email, type | | history | array | Status change history | | external_urls | array | External reference URLs | | reported_at | ISO 8601 datetime | When the incident was reported | | occurred_at | ISO 8601 datetime \| null | When the incident actually occurred | | expected_completion_date | date \| null | Expected resolution date | | due_on | date \| null | Due date | | initial_notification_sent_at | ISO 8601 datetime \| null | When the initial notification was sent | | next_checklist_item | object \| null | Next incomplete checklist item, if any | | external_reference_id | string \| null | External reference ID |