Webhooks
Tristar can deliver outbound HTTPS webhooks when customer-scoped events occur (orders, notes, invoices, and related catalog events). Receivers expose an HTTPS endpoint; Tristar POSTs a signed JSON envelope for each subscribed event.
Webhooks are outbound only. There is no public API to create or manage webhook endpoints from API v2 — configuration is done in the Admin Portal.
Configuration
In the Admin Portal: Customer → Webhooks.
For each webhook endpoint configure:
| Setting | Requirement |
|---|---|
| Name | Display name for the endpoint |
| Target URL | Absolute HTTPS URL (HTTP is rejected) |
| Secret | Shared secret used to sign delivery bodies (X-Tristar-Signature) |
| Events | One or more event codes to subscribe to |
| Active | Inactive webhooks do not receive new deliveries |
A webhook only receives events that are both subscribed on that endpoint and active in the event catalog.
Delivery history is available in Admin Portal under Webhook Logs.
Delivery Mechanics
- Application code publishes an event through
IWebhookPublisher(or the invoice detection worker enqueuesinvoice.created). - Matching active customer webhooks are enqueued as
Pendingrows inWebhookDeliveryLog(Central DB). - A background delivery worker in API v2 polls about every 15 seconds, takes up to 25 due deliveries, and POSTs each payload as
application/json. - HTTP client timeout per attempt is 30 seconds.
Retries And Statuses
| Status | Meaning |
|---|---|
Pending |
Queued, not yet successfully delivered |
Failed |
Attempt failed; will retry if under the max attempt count |
Succeeded |
Receiver returned HTTP 2xx |
DeadLetter |
Exhausted retries (5 attempts) |
Backoff after a failed attempt (before the next try):
delaySeconds = 2^attemptCount * 30
Examples after failed attempts 1–4: 60s, 120s, 240s, 480s. After the 5th failed attempt the delivery becomes DeadLetter.
Receiver Success Criteria
Treat the delivery as successful only when the receiver responds with an HTTP 2xx status. Non-2xx responses and transport errors mark the attempt failed and schedule a retry (until dead-lettered). Response bodies are stored truncated (up to 2000 characters) for troubleshooting.
Receivers should be idempotent: the same logical event may be retried, and publishers may use idempotency keys to avoid duplicate enqueue for the same webhook.
HTTP Headers
Every delivery POST includes:
| Header | Description |
|---|---|
Content-Type |
application/json; charset=utf-8 |
X-Tristar-Event |
Event code (for example order.created) |
X-Tristar-Delivery-Id |
Delivery log id (unique per queued delivery) |
X-Tristar-Signature |
HMAC-SHA256 signature of the raw request body using the webhook secret (lowercase hex). Omitted only if the webhook has no secret. |
Signature Verification
- Read the raw request body bytes as UTF-8 text (do not re-serialize JSON before verifying).
- Compute
HMAC-SHA256(secret, body). - Encode the digest as lowercase hexadecimal.
- Compare to
X-Tristar-Signatureusing a constant-time comparison.
Reject the request if the signature is missing or does not match.
Envelope Payload
Bodies are JSON with camelCase property names:
{
"id": "evt_0123456789abcdef0123456789abcdef",
"event": "order.created",
"occurredAt": "2026-07-22T15:30:00Z",
"customerCode": "ABC001",
"data": {}
}
| Field | Type | Description |
|---|---|---|
id |
string | Event id (evt_ + 32 hex chars) |
event |
string | Event code |
occurredAt |
string (ISO-8601 UTC) | When the event was enqueued |
customerCode |
string | Customer code that owns the event |
data |
object | Event-specific payload (see catalog below) |
Event Catalog
| Event code | Category | Description | Publisher status |
|---|---|---|---|
order.created |
Order | Draft or web order created | Published |
order.updated |
Order | Order fields changed (draft save/update) | Published |
order.exported |
Order | Order exported to a Winserve work order | Published |
order.status_changed |
Order | Photocopy milestone set on a status note | Published |
note.created |
Note | Note added to an order | Published |
invoice.created |
Invoice | Work-order invoice detected as newly available | Published |
order.created / order.updated
Published when order save services create or update a draft/web order.
Common data fields:
| Field | Type | Notes |
|---|---|---|
orderType |
string | Court, Process, Delivery, Investigation, FileAndServe, CountyRecording, or PhotoCopy |
webOrderId |
string | Web/draft order id |
Photo copy orders also include:
| Field | Type | Notes |
|---|---|---|
finalize |
boolean | Whether the save finalized the photo copy order |
order.exported
Published after a successful export via order export (including create-with-export flows that call export).
| Field | Type | Notes |
|---|---|---|
orderType |
string | Order type string passed into export |
webOrderId |
string | Source draft/web order id |
workOrderId |
string | Primary Winserve work order id |
courtWorkOrder |
string or null | Present for file-and-serve style exports when a court work order is created |
processWorkOrders |
array or null | Present when export creates one or more process work orders |
note.created
Published when a note is added for a customer-scoped work order.
| Field | Type | Notes |
|---|---|---|
workOrder |
string | Work order number |
workOrderType |
string | Work order type used by notes |
report |
string | Note/report text |
lineItem |
int | Created note / status identifier |
facilityLineItem |
int or null | Photocopy stop / facility line item when applicable |
order.status_changed
Published from Notes/Add when a photocopy note includes an explicit milestone value.
| Field | Type | Notes |
|---|---|---|
workOrder |
string | Photocopy work order (without facility suffix) |
orderType |
string | Currently PhotoCopy |
facilityKey |
string | {workOrder}:{facilityLineItem} when a stop id is supplied; otherwise the request work order key ({workOrder}-{facilityId}) |
facilityLineItem |
int or null | Photocopy stop / facility line item when applicable |
milestoneCode |
int | New milestone code |
milestone |
string | Milestone title when available |
previousMilestoneCode |
int or null | Prior status-line milestone when available |
previousMilestone |
string or null | Prior milestone title when available |
Example:
{
"id": "evt_d4e5f60718293a4b5c6d7e8f901a2b3c",
"event": "order.status_changed",
"occurredAt": "2026-07-22T15:33:00Z",
"customerCode": "ABC001",
"data": {
"workOrder": "W123456",
"orderType": "PhotoCopy",
"facilityKey": "W123456-01",
"milestoneCode": 1,
"milestone": "None",
"previousMilestoneCode": 1,
"previousMilestone": "None"
}
}
Desktop Winserve milestone changes (SetMilestone) are not yet wired; those paths remain a follow-up.
invoice.created
Published by a background detection worker that scans tenant invoice header tables for recently created work-order invoices (photocopy, WinservePlus/court, delivery, investigation). Idempotency keys prevent duplicate deliveries for the same invoice.
| Field | Type | Notes |
|---|---|---|
workOrder |
string | Work order number |
orderType |
string | PhotoCopy, Court, Delivery, or Investigation |
invoiceNumber |
string | Invoice number |
invoiceDate |
string or null | Invoice date when present |
billToCustomerCode |
string or null | Bill-to customer code when present |
facilityLineItem |
int or null | Photocopy facility line item when applicable |
amount |
number or null | Total when available (photocopy); otherwise null |
Example:
{
"id": "evt_e5f60718293a4b5c6d7e8f901a2b3c4d",
"event": "invoice.created",
"occurredAt": "2026-07-22T15:34:00Z",
"customerCode": "ABC001",
"data": {
"workOrder": "DD14809",
"orderType": "PhotoCopy",
"invoiceNumber": "1",
"invoiceDate": "2026-06-23T00:00:00",
"billToCustomerCode": "TRISTAR",
"facilityLineItem": 1,
"amount": 59.95
}
}
Example Delivery
POST /webhooks/tristar HTTP/1.1
Host: example.com
Content-Type: application/json; charset=utf-8
X-Tristar-Event: order.created
X-Tristar-Delivery-Id: 42
X-Tristar-Signature: 3f1a9c...lowercasehex...
{
"id": "evt_a1b2c3d4e5f60718293a4b5c6d7e8f90",
"event": "order.created",
"occurredAt": "2026-07-22T15:30:00Z",
"customerCode": "ABC001",
"data": {
"orderType": "Court",
"webOrderId": "W123456"
}
}
Verify X-Tristar-Signature against the exact body bytes shown above (or as received), using the webhook secret from Admin Portal. Respond with 2xx to acknowledge; any other status triggers retry/backoff.