Webhooks

Learn how to manage and utilize Webhooks.

Webhooks provide a convenient and performant way of programmatically alerting you when something happens within Contractors Cloud. They are the recommended approach for keeping data in-sync with Contractors Cloud on your end and also to listen and react to specific events.

Example scenario

Say for example that whenever a Project is created in Contractors Cloud, you have business rules on your end that need to occur. Perhaps a custom email needs to go out to your sales department or the newly created Project data needs to be put in an Excel spreadsheet or database to be kept track of on your end. Whatever that case(s) may be, Webhooks will allow you to do just that.

⚠️ Keep in mind

If you find yourself constantly polling the API to look for new changes, updates and/or deletions, this is usually a good sign that you should instead be using Webhooks for your approach.

Setup

Currently, Webhooks can only be setup via the API. We are in the process of developing their corresponding UI within the Contractors Cloud web app. Webhooks have their own set of API endpoints that allow you to fully manage them programmatically if you choose to do so. In order to successfully setup a Webhook, you will need to provide a full, secure url for the Webhook to broadcast events to. Typically, this url will point to an internal app or service on your end that you can customize to respond to Webhooks.

From our above example scenario, if you create a Webhook that points to

https://my-custom-app.com/contractors-cloud/webhooks

and you subscribe your Webhook to the projects.created event, anytime a Project is created within the scope of your Contractors Cloud account, a POST call with the event and Webhook information will be made to your url along with the newly created Project data.

Example Webhook payload

Here is an example Webhook payload:

{
  "event": "projects.created",
  "description": "Occurs whenever a Project is created.",
  "webhook_id": "9b0fdcfe-316e-45c7-95ba-a941f2c66f2f",
  "data": {
    "id": 1674684,
    "tenant_id": 1,
    "company_id": 6,
    "account_id": 2206779,
    "status_id": null,
    "event_id": 8,
    "lead_id": 7870,
    "storm_id": null,
    "group_id": null,
    "workflow_id": null,
    "site_type_id": 11,
    "cancellation_id": null,
    "rep_primary_id": 10007,
    "original_milestone_id": null,
    "current_project_milestone_id": null,
    "referred_by": 1470,
    "hash": "TVRZM05EWTROQT09",
    "number": null,
    "name": "Roofing job in Delano",
    "address_street": "123 Example Street",
    "address_city": "Flavortown",
    "address_state": "MN",
    "address_zip": "55328",
    "address_description": null,
    "address_latitude": "-69.380796",
    "address_longitude": "-147.963345",
    "needs_appointment": false,
    "rep_primary_name": "Mike Kampa",
    "percent_completed": null,
    "amount_estimated": "0.00",
    "percent_sale_close": "0.00",
    "sale_closed_at": "2024-01-11 10:38:55",
    "referred_at": "2024-01-11 10:38:55",
    "created_at": "2024-01-11 10:38:55",
    "updated_at": "2024-01-11 10:38:55",
    "deleted_at": null
  }
}

Secure your Webhooks

In order to keep your Webhook url secure, it's vital that proper authorization is in place. After all, you wouldn't want anyone to make POST calls to your url with arbitrary data, effectively compromising your system. Hence, your Webhook url should only authorize incoming requests from Contractors Cloud and no one else.

To achieve proper authorization, a singing secret is provided to you when you create a Webhook. Furthermore, incoming Webhook requests from Contractors Cloud will contain a Contractorscloud-Signature header with a timestamp and verification hash (signature). Contractors Cloud generates signatures using a hash-based message authentication code (HMAC) with SHA-256. For example:

Contractorscloud-Signature: t=1705020657,v=a49e1042e5b3352c76a74c7f334742ff42e8c411eaa33e7add36...

To validate that the incoming request is authentic, you will need to do the following:

Step 1: Extract the timestamp and signature from the header

Split the header using the , character as the separator to get a list of elements. Then split each element using the = character as the separator to get a prefix and value pair.

The value for the prefix t corresponds to the timestamp, and v corresponds to the signature. You can discard all other elements.

Step 2: Prepare the signed_payload string

The signed_payload string is created by concatenating:

  • The timestamp (as a string)
  • The character .
  • The actual JSON payload (that is, the request body)

⚠️ Warning

Webhook validation requires the raw body of the request to perform signature verification. If you’re using a framework, make sure it doesn’t manipulate the raw body. Any manipulation to the raw body of the request will cause the verification to fail.

Step 3: Determine the expected signature

Compute an HMAC with the SHA256 hash function. Use the endpoint’s signing secret as the key, and use the signed_payload string as the message.

Step 4: Compare the signatures

Compare the signature in the header to the expected signature. For an equality match, compute the difference between the current timestamp and the received timestamp, then decide if the difference is within your tolerance.

To protect against timing attacks, use a constant-time-string comparison to compare the expected signature to each of the received signatures.

Step 5: Quickly return a response

Your endpoint must quickly return a successful status code (2xx) prior to any complex logic that could cause a timeout. For example, you must return a 200 response before updating Project data and sending out emails on your end. If the incoming request fails authorization, we recommend you return a 401 status code. If you fail to return any type of response within 5 seconds of receiving the request, the corresponding Webhook status will be marked as "timeout".

Example verification code

Here is an example PHP function that calculates the incoming signature and compares it to the one received:

/**
 * Determines if an incoming Webhook is valid.
 * 
 * @param int $timestamp    The "t=" value from the header.
 * @param string $signature The "v=" value from the header.
 * @param string $payload   The raw body of the request exactly as it came in as a string.
 * @param string $secret    The Webhook signing secret.
 * @return bool             Returns true if the incoming Webhook is authentic and false if not.
 */
function isWebhookValid(int $timestamp, string $signature, string $payload, string $secret): bool
{
   $calculatedSignature = hash_hmac('sha256', $timestamp.'.'.$payload, $secret);

   return hash_equals($calculatedSignature, $signature);
}