Tradesmen Technologies Tradesmen Technologies

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:
    {
      "source": "Zapier",
      "allowJobCreation": true,
      "notifyCompanyOnNewCustomer": true
    }
    
    Note: source is a required string. The company is determined from the authenticated user's token. allowJobCreation is an optional boolean that controls whether the webhook code can create jobs (defaults to true when not specified). notifyCompanyOnNewCustomer is an optional boolean; when true, a text message is sent to the company's phone number each time the webhook creates a new customer (defaults to false when not specified or absent on existing webhooks).
  • Success Response: 201 Created
    {
      "code": "ABCDEFghijklmnopQRSTUVwxyz012345",
      "source": "Zapier",
      "allowJobCreation": true,
      "notifyCompanyOnNewCustomer": true
    }
    
    Note: code is a randomly generated 32-character alphanumeric string. allowJobCreation and notifyCompanyOnNewCustomer are only included in the response when explicitly provided in the request.
  • Error Responses:
    • 400 Bad Request: Missing body, missing source, or allowJobCreation/notifyCompanyOnNewCustomer is 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:
    {
      "allowJobCreation": false,
      "notifyCompanyOnNewCustomer": true
    }
    
    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.
  • Success Response: 200 OK
    {
      "message": "Webhook code updated successfully",
      "code": "ABCDEFghijklmnopQRSTUVwxyz012345",
      "source": "Zapier",
      "allowJobCreation": false,
      "notifyCompanyOnNewCustomer": true
    }
    
    Note: allowJobCreation and notifyCompanyOnNewCustomer reflect the stored values after the update; either may be absent if it has never been set on the webhook.
  • Error Responses:
    • 400 Bad Request: Missing code path 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: Missing code path 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
    {
      "companyName": "Acme Plumbing",
      "source": "Zapier",
      "allowJobCreation": true,
      "notifyCompanyOnNewCustomer": false
    }
    
    Note: allowJobCreation and notifyCompanyOnNewCustomer always carry a boolean — a webhook that never had the flag set reports the incoming webhook's defaults (true / false). companyName and source are null when not stored on the webhook.
  • Error Responses:
    • 400 Bad Request: Missing code path 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): email must be a syntactically valid address (exactly one @, a domain with a top-level domain, no spaces; 400 otherwise) 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 an emailWarning (see Email deliverability flags in the Customers documentation); additionalEmails must be an array of valid addresses (blank entries dropped, the rest normalized); phoneNumber must be a valid 10-digit US number and is stored normalized to E.164 (+1…); address must be an object (its subfields are optional); additionalAddresses must be an array of objects; additionalPhoneNumbers must be an array of valid 10-digit US numbers (each normalized like phoneNumber); customFields must be an object mapping custom field IDs to values; smsOptedOut must 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, and source default to null; the three additional* lists default to []; email, phoneNumber, customFields, and smsOptedOut are absent from the stored record. Every created customer is stamped with a server-generated createdAt.

    Legacy compatibility: if a caller still sends a notes string in the payload, it is treated as description (only used when description is not also provided). The customer notes field itself is reserved for timestamped note entries created via POST /customers/customers/{customerId}/notes.

    Optional job object (every field optional; same rules as Create Job): when present, a job is created for the customer with customerId set to the created/matched customer. jobType defaults to OTHER and must be a valid JobType; status defaults to NEW and must be a default JobStatus or, when pipelineId names one of the company's pipelines, one of that pipeline's statuses (400 Pipeline not found otherwise); employeeIds defaults to [] and must be an array of strings; description defaults to the webhook's source label; startDateTime, endDateTime, salesEmployeeId, and color default to null; address and destinationAddress are optional, but when provided must contain streetAddress, city, state, and zipCode (an address sent as null or as an object of empty strings is treated as not provided); customFields must be an object. The job is created exactly like Create Job: it gets a company-scoped sequential displayId, assigned employees are texted when the company's notifyEmployeeOnJobAssignment flag is on, connected Google Calendars are synced, a job created directly as COMPLETED/PAID runs the inventory deduction, and a job created directly as IN_PROGRESS starts its in-progress period (see In-Progress Time in jobs-endpoints.md). recurrence and programId are 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 has allowJobCreation set to false.

  • Success Responses:

    New customer created — 201 Created:

    {
      "message": "Customer created successfully",
      "customerId": "uuid",
      "jobId": "uuid",
      "displayId": 12,
      "customerIsNew": true
    }
    

    Note: jobId and displayId are only present if a job object 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: jobId and displayId are only present if a job object 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, address not an object), or a job field fails validation (job not an object, invalid jobType/status, pipelineId not a non-empty string or unknown, incomplete address/destinationAddress, employeeIds not an array of non-empty strings, description/startDateTime/endDateTime/salesEmployeeId/color not strings, customFields not an object).
    • 403 Forbidden: Invalid webhook code, or webhook code does not allow job creation (when a job object is provided and allowJobCreation is false).
    • 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.