Webhooks - Expected 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

{
  "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 an invoice file is gathered
  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.

{
  "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"
  }
}

OCR Vendor Invoice Events

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

  1. OCRVendorInvoiceWasCreated — Extraction job initiated
  2. OCRVendorInvoiceWasExtracted — Extraction complete
  3. OCRVendorInvoiceWasUpdated — Manual corrections and/or updates 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.

{
  "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 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 (QuickBooks Online or QuickBooks Desktop), including force posts

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

Note: VendorInvoiceWasPosted is not sent when:

  • an invoice is Manually Posted (tenants without an accounting integration);
  • an invoice is unposted or rolled back to Approved;
  • an invoice becomes Paid, or a balance sync from QuickBooks Online changes its balance.

None of these send any webhook. VendorInvoiceWasUpdated is only sent when the invoice’s details are edited (its invoice number, invoice date, fiscal period or short pay) or when it’s updated through the API.

For QuickBooks Desktop, the event fires when the invoice is posted in Discovery, before it’s included in an .iif export, so external_accounting_id is empty in the payload. For QuickBooks Online, it holds the ID of the bill (or vendor credit) in QuickBooks.


Example — VendorInvoiceWasCreated:

Additional internal fields are omitted for brevity.

{
  "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"
  }
}

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.

{
  "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"
  }
}

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.

{
  "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",
    "external_urls": [
      {"url": "https://example.com/hauler-portal/4471", "name": "Hauler-ticket 4"}
     ],
    "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.

{
  "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 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.

{
  "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"
  }
}

Client Invoice Events

Four lifecycle events fire across the client invoice lifecycle:

  1. ClientInvoiceWasCreated – Fired when a new client invoice is created
  2. ClientInvoiceWasUpdated – Fired when a client invoice's details are updated (e.g. notes, line items rebuilt, totals recalculated)
  3. ClientInvoiceWasSent – Fired when a client invoice is emailed to the client
  4. ClientInvoiceStatusWasChanged – Fired whenever a client invoice transitions between statuses

Note: Line items are a related object and are not included in the webhook payload. Use the Discovery API to retrieve line item details.

Example - ClientInvoiceWasSent

This event fires when a client invoice is emailed to the client. The payload is intentionally minimal — use the id      to fetch full invoice details via the API if needed.


{
  "type": "ClientInvoiceWasSent",
  "data": {
    "id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
    "timestamp": "2026-04-15T10:30: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

If you have additional questions or need more in depth information, please feel free to send us a message using the help beacon in the lower right-hand corner.