Audience: Developers and integration partners building webhook consumers against TCS. Status: v1 — Production Scope: Job lifecycle events only. For customer, lead-source, and quote data, use the v3 Public API.
Overview
TCS dispatches outbound HTTPS webhooks when jobs change state. A single endpoint, configured per branch, receives events for all job operations. Each event carries a JSON payload describing the entity that changed and the operation that triggered the dispatch.
The webhook system is intended for partners who want to:
- Mirror job activity into an external CRM, marketing platform, or data warehouse
- Trigger downstream workflows (review requests, follow-up sequences, attribution reports)
- Maintain an audit log of operational events outside TCS
For data that is not part of the job lifecycle (customer profile, lead source, marketing attribution, quote history), use the v3 Public API endpoints. Job webhooks intentionally carry only fields directly tied to the job; cross-entity context belongs in the API surface where it can be queried on demand.
Configuration
Webhooks are configured per branch. A branch can register multiple webhook URLs, each scoped to a specific entity and operation (for example: one URL for job complete, a different URL for job rated).
To configure: Branch Settings → API Credentials tab (visible to the company owner only). The tab provides:
- Branch API key for the v3 Public API
- API endpoint URL
- Webhook URL entries — one per (entity, operation) pair you want to receive
Saving a URL activates dispatch immediately. Clearing it stops dispatch.
Creating or editing webhooks requires the company owner role. Non-owner accounts can view the tab and existing webhooks but cannot save changes.
Delivery semantics
- Transport: HTTPS POST, JSON body
- Authentication: Configurable per branch (basic auth supported)
- Timeout: Approximately 5 seconds per delivery attempt
- Retries: Best-effort; a more robust retry policy is on the roadmap. Consumers should be idempotent and tolerate occasional missed events by reconciling against the v3 Public API on a cadence appropriate to the integration.
- Ordering: Not guaranteed. Consumers should treat each event as a snapshot of the job at dispatch time and resolve out-of-order delivery by timestamp.
Operations
A single job can produce multiple events over its lifetime. The operation field on the payload identifies which event fired.
Lifecycle operations
These fire when job_status transitions to a new value.
| Operation | Trigger |
job_status value |
|---|---|---|
create |
Job is created |
ACT (Unassigned) |
assign |
Job is assigned to a team |
ASI (Assigned) |
start |
Team starts the job |
PRO (In Progress) |
complete |
Job is completed |
COM (Completed) |
cancel |
Job is cancelled |
CAN (Cancelled) |
reschedule |
Job is moved to a different date |
RES (Rescheduled) |
skip |
Job is skipped (subscription) |
SKI (Skipped) |
bump |
Job is bumped to the next slot |
BUM (Bumped) |
Synthetic operations
These fire when specific fields change, independent of job_status. They allow consumers to react to events that do not map to a status transition.
| Operation | Trigger |
|---|---|
rated |
Customer rating or comment is recorded (customer_rate, customer_comment) |
tipped |
Tip is recorded (tip_amount, tip_amount_extra) |
charged |
Payment status changes (payment_status) |
Payload envelope
Every webhook delivery has the same top-level shape:
{
"entity_type": "job",
"operation": "complete",
"data": { ... }
}
| Field | Type | Description |
|---|---|---|
entity_type |
string | Always "job" for events documented on this page. Future event types (leads, deals) will use other values. |
operation |
string | One of the operations listed above. |
data |
object | Snapshot of the job. Field reference below. |
Data field reference
The data object carries a snapshot of the job at the moment the event was dispatched. All fields are present on every operation unless marked otherwise; fields not applicable to a given job are returned as null.
Identity and association
| Field | Type | Description |
|---|---|---|
id |
integer | Unique job identifier |
branch_id |
integer | Branch that owns the job |
branch_name |
string | Display name of the branch |
customer_id |
integer | Customer the job is for. Use this with the v3 Public API /customer endpoint to retrieve customer profile, contact info, and lead source. |
customer_first_name |
string | Customer first name |
customer_last_name |
string | Customer last name |
subscription_id |
integer or null | Set if the job belongs to a recurring subscription; null for one-time jobs |
subscription_frequency |
string or null | Recurrence label when subscription_id is set (e.g. "Weekly", "Biweekly", "Monthly"). Null for one-time jobs. |
service_id |
integer | Service catalog identifier |
service_name |
string | Service display name (e.g. "Deep Clean") |
address_id |
integer | Service address identifier |
address |
string | Formatted service address |
Assignment
| Field | Type | Description |
|---|---|---|
team_id |
integer or null | Assigned team identifier; null before assign
|
team_number |
integer or null | Branch-local team number |
driver_id |
integer or null | Assigned driver identifier; null before assign
|
driver_first_name |
string or null | Driver first name |
driver_last_name |
string or null | Driver last name |
time_slot_id |
integer or null | Time slot identifier |
time_slot_start |
string or null | Slot start time (HH:MM) |
time_slot_end |
string or null | Slot end time (HH:MM) |
Lifecycle and timing
| Field | Type | Description |
|---|---|---|
job_status |
string | Three-letter status code; see Job status codes below |
commercial |
boolean | True for commercial jobs, false for residential |
completion_deadline |
date | Date by which the job must be completed |
created_date |
datetime | When the job was created |
assigned_date |
datetime or null | When the job was first assigned |
start_time |
datetime or null | When the team started the job (set at start) |
end_time |
datetime or null | When the team finished the job (set at complete) |
performed_date |
datetime or null | Effective performance date (typically equals end_time) |
completed_date |
datetime or null | When the job entered COM status |
Pricing and payment
| Field | Type | Description |
|---|---|---|
total_price |
decimal | Total amount charged (or to be charged) for the job |
payment_status |
string | Three-letter payment status code; see Payment status codes below |
payment_error |
string or null | Last payment error message, if any |
cleaner_earnings_status |
string | Three-letter payroll status code; see Cleaner earnings status codes below |
Interruption and adjustments
| Field | Type | Description |
|---|---|---|
lost_date |
datetime or null | When the job was marked lost (cancelled with no replacement) |
lost_reason |
string or null | Free-text reason for marking lost |
cancel_charge |
decimal | Cancellation fee charged, if any |
moved_to_date |
date or null | Target date when rescheduled |
moved_to_job_id |
integer or null | Identifier of the job created by the reschedule |
skip_charge |
decimal | Skip fee charged, if any |
skip_count |
integer | Number of times this subscription's slot has been skipped consecutively. Used internally to apply skip-streak rules. |
bump_discount |
decimal | Discount applied when this job was bumped |
Operation-specific fields
These fields are only populated on the operations indicated. On all other operations they are null.
rated only
| Field | Type | Description |
|---|---|---|
customer_rate |
integer | Customer rating |
customer_comment |
string or null | Customer free-text comment |
customer_rate_date |
datetime | When the rating was recorded |
tipped only
| Field | Type | Description |
|---|---|---|
tip_amount |
decimal | Tip recorded for this job |
tip_amount_extra |
decimal | Additional tip beyond the initial amount |
upfront_tip_amount |
decimal | Upfront tip captured at quote time |
is_upfront_tip_recurring |
boolean | True if the upfront tip applies to every job in the subscription |
Status code dictionaries
Three-letter abbreviations are used for compactness. Plain-English mappings are below. A future revision of the webhook payload will expand these to user-friendly values; until then, consumers should map the codes themselves using these tables.
Job status codes
| Code | Meaning |
|---|---|
ACT |
Unassigned (active, awaiting team assignment) |
ASI |
Assigned (team assigned, not yet started) |
PRO |
In Progress (team has started the job) |
COM |
Completed |
CAN |
Cancelled |
LOC |
Lockout (team could not access the property) |
RES |
Rescheduled (moved to a different date) |
SKI |
Skipped (subscription occurrence skipped) |
BUM |
Bumped (moved to next available slot) |
Payment status codes
| Code | Meaning |
|---|---|
COM |
Paid via Stripe |
CIN |
Paid via Invoice |
CCC |
Paid via Cash or Check |
COO |
Paid via other method |
INC |
Invoice created (not yet sent) |
INS |
Invoice sent |
MIS |
Monthly invoice sent (consolidated) |
INV |
Invoicing in progress |
PIP |
Payment in process |
PER |
Payment error |
UNP |
Unpaid |
NCH |
No charge required |
Cleaner earnings status codes
| Code | Meaning |
|---|---|
ACT |
Active (earnings recorded against the job, available for the next payroll run) |
PEN |
Settled (earnings have been paid out in a payroll batch) |
Customer, lead, and attribution data
Job webhooks deliberately do not include customer-level fields such as lead source, marketing channel, contact preferences, or quote history. These belong to the customer entity, not the job, and packing them into every job event would duplicate data and couple consumers to a wider surface than the job event needs.
To enrich a webhook event with customer-level context:
- Receive the job webhook
- Take
customer_idfrom the payload - Query the v3 Public API
/customer/{id}endpoint for the customer profile, includinglead_source, contact details, and other attribution fields - Cache the customer record locally if your integration processes high event volume
Lead, deal, and quote events are planned as a separate webhook stream. See Roadmap.
Known limitations
The following are known gaps in v1. They are scheduled for a follow-up release; integrations should design around them until then.
-
Driver name and ID:
driver_id,driver_first_name, anddriver_last_namemay be null oncompleteevents even when a driver was assigned. This affects jobs that go through certain assignment paths. As a workaround, consumers can resolve the driver fromteam_idvia the v3 Public API. - Multiple drivers per job: When a job is performed by more than one driver (for example, a team where each member drives separately), the payload reports a single driver. Multi-driver representation is on the roadmap.
-
Time-slot timestamps: For some legacy jobs,
time_slot_startandtime_slot_endmay surface as a default epoch date. Consumers should treat any year value of 1970 as "no time slot" and fall back tostart_time/end_time. -
Status code abbreviations: The three-letter codes for
job_status,payment_status, andcleaner_earnings_statusare not user-friendly. Use the dictionaries above to map them. A future payload version will deliver expanded values alongside the codes. - Delivery reliability: Retries are best-effort. For integrations where missed events would cause material data loss, reconcile periodically against the v3 Public API.
Roadmap
The following events are planned for future webhook releases. They are not available today.
Additional job operations
-
payroll_settled— fires whencleaner_earnings_statustransitions to settled -
clocked_in/clocked_out— fires when team members clock in or out for the job -
break_started/break_ended— fires on team break events -
drive_started— fires when the team begins driving to the job -
arrived— fires when the team arrives at the service address
Lead and deal events
A separate webhook stream for sales-stage events is planned. This will cover:
- Lead created, qualified, and lost
- Deal status changes (booked, converted, abandoned)
- Quote status changes
This stream will let CRM and marketing integrations close the loop with operations: the marketing platform updates a lead's status when TCS marks it booked, ending the follow-up sequence; conversion data flows back to the marketing platform for attribution.
Reliability improvements
- Delivery queue with stronger retry semantics
- Configurable timeout
- Per-event acknowledgement and replay
Example payload
A complete event for a residential weekly subscription job:
{
"entity_type": "job",
"operation": "complete",
"data": {
"id": 482711,
"branch_id": 12,
"branch_name": "Holly's Cleaning",
"customer_id": 31507,
"customer_first_name": "John",
"customer_last_name": "Smith",
"subscription_id": 8842,
"subscription_frequency": "Weekly",
"service_id": 2,
"service_name": "Deep Clean",
"address_id": 41902,
"address": "123 Main St, Boston MA 02118",
"team_id": 5,
"team_number": 3,
"driver_id": 717,
"driver_first_name": "Maria",
"driver_last_name": "Lopez",
"time_slot_id": 4,
"time_slot_start": "09:00",
"time_slot_end": "11:00",
"job_status": "COM",
"commercial": false,
"completion_deadline": "2026-04-13",
"created_date": "2026-04-01T10:00:00",
"assigned_date": "2026-04-02T08:00:00",
"start_time": "2026-04-13T09:05:00",
"end_time": "2026-04-13T11:10:00",
"performed_date": "2026-04-13T11:10:00",
"completed_date": "2026-04-13T11:10:00",
"total_price": 150.00,
"payment_status": "COM",
"payment_error": null,
"cleaner_earnings_status": "ACT",
"lost_date": null,
"lost_reason": null,
"cancel_charge": 0.00,
"moved_to_date": null,
"moved_to_job_id": null,
"skip_charge": 0.00,
"skip_count": 0,
"bump_discount": 0.00,
"customer_rate": null,
"customer_comment": null,
"customer_rate_date": null,
"tip_amount": null,
"tip_amount_extra": null,
"upfront_tip_amount": null,
"is_upfront_tip_recurring": null
}
}
Support
For webhook configuration, debugging, or feature requests, contact TCS support.
Comments
0 comments
Please sign in to leave a comment.