API reference Webhooks
Webhooks Service API Endpoints
The production API base URL is https://api.tradesmentechnologies.com/.
Authentication
Most endpoints require a Bearer token in the Authorization header:
Authorization: Bearer <your_token>
Mutating requests may carry an Idempotency-Key header so a retry never acts twice — see Idempotent Retries in the Accounts API documentation.
The Incoming Webhook endpoint is public and does not require authentication; the webhook code in the URL acts as the access key.
Base Path
/prod/webhooks
Endpoints
Create Webhook
Create a new webhook for a company.
- Method: POST
- Path:
/prod/webhooks - Authentication: Required (Bearer Token)
- Request Body:
Note:{ "source": "Zapier", "allowJobCreation": true, "notifyCompanyOnNewCustomer": true }sourceis a required string. The company is determined from the authenticated user's token.allowJobCreationis an optional boolean that controls whether the webhook code can create jobs (defaults totruewhen not specified).notifyCompanyOnNewCustomeris an optional boolean; whentrue, a text message is sent to the company's phone number each time the webhook creates a new customer (defaults tofalsewhen not specified or absent on existing webhooks). - Success Response:
201 Created
Note:{ "code": "ABCDEFghijklmnopQRSTUVwxyz012345", "source": "Zapier", "allowJobCreation": true, "notifyCompanyOnNewCustomer": true }codeis a randomly generated 32-character alphanumeric string.allowJobCreationandnotifyCompanyOnNewCustomerare only included in the response when explicitly provided in the request. - Error Responses:
400 Bad Request: Missing body, missingsource, orallowJobCreation/notifyCompanyOnNewCustomeris not a boolean.401 Unauthorized: Missing or invalid token.404 Not Found: Company not found.
Update Webhook
Update the toggleable settings of an existing webhook. Ownership is verified against the authenticated user's company.
- Method: PATCH
- Path:
/prod/webhooks/{companyName}/{code} - Authentication: Required (Bearer Token)
- Path Parameters:
companyName: Present in the URL for backward compatibility but ignored by the handler. The code is looked up by its value.code: The webhook code to update.
- Request Body:
Note: Both fields are optional booleans, but at least one must be provided. Only the fields included in the request are changed; omitted fields are left untouched.{ "allowJobCreation": false, "notifyCompanyOnNewCustomer": true } - Success Response:
200 OK
Note:{ "message": "Webhook code updated successfully", "code": "ABCDEFghijklmnopQRSTUVwxyz012345", "source": "Zapier", "allowJobCreation": false, "notifyCompanyOnNewCustomer": true }allowJobCreationandnotifyCompanyOnNewCustomerreflect the stored values after the update; either may be absent if it has never been set on the webhook. - Error Responses:
400 Bad Request: Missingcodepath parameter, missing body, a non-boolean field value, or neither updatable field provided.401 Unauthorized: Missing or invalid token.403 Forbidden: The authenticated user's company does not own this webhook code.404 Not Found: Webhook code not found.
Delete Webhook
Delete a webhook by code value. Ownership is verified against the authenticated user's company.
- Method: DELETE
- Path:
/prod/webhooks/{companyName}/{code} - Authentication: Required (Bearer Token)
- Path Parameters:
companyName: Present in the URL for backward compatibility but ignored by the handler. The code is looked up by its value.code: The webhook code to delete.
- Success Response:
200 OK{ "message": "Webhook code deleted successfully" } - Error Responses:
400 Bad Request: Missingcodepath parameter.401 Unauthorized: Missing or invalid token.403 Forbidden: The authenticated user's company does not own this webhook code.404 Not Found: Webhook code not found.
Verify Webhook Code (connection test)
Public endpoint for code-authenticated integrations (the Zapier app uses it as its API-key connection test). Confirms a webhook code is valid and returns the settings the integration needs to label the connection. Never returns the code, the company id, or any other secret.
- Method: GET
- Path:
/prod/webhooks/incoming/{code} - Authentication: None (the webhook code in the path is the credential)
- Path Parameters:
code: The webhook code to verify.
- Success Response:
200 OK
Note:{ "companyName": "Acme Plumbing", "source": "Zapier", "allowJobCreation": true, "notifyCompanyOnNewCustomer": false }allowJobCreationandnotifyCompanyOnNewCustomeralways carry a boolean — a webhook that never had the flag set reports the incoming webhook's defaults (true/false).companyNameandsourcearenullwhen not stored on the webhook. - Error Responses:
400 Bad Request: Missingcodepath parameter.403 Forbidden: Invalid webhook code.500 Internal Server Error: Unexpected server error.
Incoming Webhook (Create Customer + Optional Job)
Public endpoint for external integrations (e.g., Zapier) to create a customer and optionally a job. The webhook code in the URL acts as the access key. No endpoint-specific rate limit is configured; only API Gateway's account-level default throttle applies.
Payload parity with the app's endpoints: the customer fields are exactly the fields of a single customer object in Create Customers (customers-endpoints.md) and the job object takes the fields of Create Job (jobs-endpoints.md), with the same validation rules and defaults. Every field is optional — a customer can be created from an empty body {}, and a job from "job": {}. The only additions are the webhook-specific idempotencyKey, and the exclusions are customerId on the job (always the created/matched customer), recurrence, and programId (not exposed to integrations).
Duplicate Prevention: If a customer with the same email already exists for the company, the existing customer is used instead of creating a duplicate. The lookup tries the lowercased address first and then the address exactly as sent, so customers stored before addresses were normalized (2026-09-25) still match when the integration keeps sending the same spelling. When no email is sent but a phoneNumber is, the same check runs on the normalized phone number. When neither is sent, a new customer is always created.
Idempotency: An optional idempotencyKey field provides full replay protection. If the matched existing customer (by email or phone, as above) carries the same idempotencyKey, the request returns the existing data without creating anything.
New-customer notification: If the webhook code has notifyCompanyOnNewCustomer set to true, a text message is sent to the company's phone number whenever a brand-new customer is created (it does not fire when an existing customer is reused). The message includes the webhook's source label plus whichever of the new customer's name, phone number, and email were provided. Delivery is best-effort and never affects the webhook's response.
Method: POST
Path:
/prod/webhooks/incoming/{code}Authentication: None (public endpoint)
Path Parameters:
code: The webhook code that identifies the company.
Request Body: (every field optional)
{ "firstName": "Jane", "middleName": "Marie", "lastName": "Smith", "email": "jane@example.com", "phoneNumber": "1234567890", "address": { "streetAddress": "123 Main St", "city": "Springfield", "state": "IL", "zipCode": "62701" }, "description": "Optional description", "additionalEmails": ["optional@example.com"], "additionalPhoneNumbers": ["4445556666"], "additionalAddresses": [ { "streetAddress": "789 Second St", "city": "Other City", "state": "IL", "zipCode": "67890" } ], "parentCategoryId": "category-uuid", "customFields": { "field-uuid-1": "ACME-123" }, "smsOptedOut": false, "source": "Optional source", "idempotencyKey": "unique-request-id", "job": { "jobType": "OTHER", "status": "NEW", "description": "Optional job description", "startDateTime": "2024-01-01T10:00:00.000Z", "endDateTime": "2024-01-01T14:00:00.000Z", "employeeIds": ["employee-uuid"], "salesEmployeeId": "employee-uuid", "pipelineId": "pipeline-uuid", "color": "#FF0000", "customFields": { "fieldId-1": "string value" }, "address": { "streetAddress": "123 Main St", "city": "Springfield", "state": "IL", "zipCode": "62701" }, "destinationAddress": { "streetAddress": "456 Oak Ave", "city": "Chicago", "state": "IL", "zipCode": "60601" } } }Customer fields (all optional; same rules as Create Customers):
emailmust be a syntactically valid address (exactly one@, a domain with a top-level domain, no spaces;400otherwise) and is stored trimmed and lowercased; its domain is checked for a mail server and a brand-new customer whose domain has none (or is reserved, e.g.example.com) is still created but carries anemailWarning(see Email deliverability flags in the Customers documentation);additionalEmailsmust be an array of valid addresses (blank entries dropped, the rest normalized);phoneNumbermust be a valid 10-digit US number and is stored normalized to E.164 (+1…);addressmust be an object (its subfields are optional);additionalAddressesmust be an array of objects;additionalPhoneNumbersmust be an array of valid 10-digit US numbers (each normalized likephoneNumber);customFieldsmust be an object mapping custom field IDs to values;smsOptedOutmust be a boolean (false= opted in to text messages — only stored when a brand-new customer is created, never overwritten on an existing customer). Defaults when omitted:firstName,middleName,lastName,description,address,parentCategoryId, andsourcedefault tonull; the threeadditional*lists default to[];email,phoneNumber,customFields, andsmsOptedOutare absent from the stored record. Every created customer is stamped with a server-generatedcreatedAt.Legacy compatibility: if a caller still sends a
notesstring in the payload, it is treated asdescription(only used whendescriptionis not also provided). The customernotesfield itself is reserved for timestamped note entries created viaPOST /customers/customers/{customerId}/notes.Optional
jobobject (every field optional; same rules as Create Job): when present, a job is created for the customer withcustomerIdset to the created/matched customer.jobTypedefaults toOTHERand must be a validJobType;statusdefaults toNEWand must be a defaultJobStatusor, whenpipelineIdnames one of the company's pipelines, one of that pipeline's statuses (400 Pipeline not foundotherwise);employeeIdsdefaults to[]and must be an array of strings;descriptiondefaults to the webhook'ssourcelabel;startDateTime,endDateTime,salesEmployeeId, andcolordefault tonull;addressanddestinationAddressare optional, but when provided must containstreetAddress,city,state, andzipCode(an address sent asnullor as an object of empty strings is treated as not provided);customFieldsmust be an object. The job is created exactly like Create Job: it gets a company-scoped sequentialdisplayId, assigned employees are texted when the company'snotifyEmployeeOnJobAssignmentflag is on, connected Google Calendars are synced, a job created directly asCOMPLETED/PAIDruns the inventory deduction, and a job created directly asIN_PROGRESSstarts its in-progress period (see In-Progress Time injobs-endpoints.md).recurrenceandprogramIdare not supported here. The job is validated before anything is written, so a rejected job never leaves a customer behind. Job creation is blocked if the webhook code hasallowJobCreationset tofalse.Success Responses:
New customer created —
201 Created:{ "message": "Customer created successfully", "customerId": "uuid", "jobId": "uuid", "displayId": 12, "customerIsNew": true }Note:
jobIdanddisplayIdare only present if ajobobject was provided in the request.Existing customer found, new job created —
200 OK:{ "message": "Existing customer found", "customerId": "uuid", "jobId": "uuid", "displayId": 12, "customerIsNew": false }Note:
jobIdanddisplayIdare only present if ajobobject was provided in the request.Idempotent duplicate detected —
200 OK:{ "message": "Duplicate request detected", "customerId": "uuid", "customerIsNew": false }Note: Returned when the matched existing customer carries the same
idempotencyKey. No new records are created.Error Responses:
400 Bad Request: Missing request body, body is not a JSON object, a customer field fails validation (e.g. invalid phone number,addressnot an object), or ajobfield fails validation (jobnot an object, invalidjobType/status,pipelineIdnot a non-empty string or unknown, incompleteaddress/destinationAddress,employeeIdsnot an array of non-empty strings,description/startDateTime/endDateTime/salesEmployeeId/colornot strings,customFieldsnot an object).403 Forbidden: Invalid webhook code, or webhook code does not allow job creation (when ajobobject is provided andallowJobCreationisfalse).404 Not Found: Company not found.429 Too Many Requests: Only from API Gateway's account-level throttle — there is no endpoint-specific limit.500 Internal Server Error: Unexpected server error.