Developer Center
Getting Started
Using Jobber’s API
Building Your App
Publishing Your App
App Template Project
Custom Integrations
Changelog

Setting up Webhooks

Webhooks allow for real-time data to be sent to your application when an event occurs on models within Jobber.

Webhooks are configured at the application level and will trigger when an account with an installation performs the topic. As an example, if you've configured a webhook to listen to CLIENT_CREATE, whenever a user creates a new client, the URL provided will be sent a POST request with the details in the body. To see what details are sent, see the payload section.

Delivery

It is the responsibility of the app developer to monitor that all entered Webhook URLs can successfully receive an HTTP request. Jobber's Developer Center does not currently send automated notifications for unexpected Webhook responses (eg. HTTP status codes outside of the 2xx range).

Webhook requests must be responded to within 1 second of receipt. To ensure adherence to this limit, webhook payloads must be processed asynchronously by the receiving system; your app needs to be resilient to a sudden high volume of requests.

If an app's response times are outside of this limit, or if a large proportion of its responses are errors requiring retries, Jobber may disable that app's webhooks to limit the impact on other apps and systems.

Jobber webhooks provide at-least-once delivery: in certain circumstances the same webhook may be sent multiple times. For example, if initial delivery appears to fail due to network timeout, the webhook will be re-sent. Apps should detect duplicate webhooks based on the payload data and handle them in an idempotent manner.

Authorization

To receive webhook data for a topic, you must have the appropriate read scope for that topic. For example, you must have access to read clients to access any of the CLIENT_X topics.

Supported Topics

To see a list of supported topics, see WebHookTopicEnum in our GraphQL schema. All supported topics are selectable when creating a webhook in the configuration of your application.

Examples:

  • CLIENT_CREATE
  • CLIENT_UPDATE
  • CLIENT_DESTROY

Webhook Payload

The webhook payload sent to the provided URL is introspectable in our GraphQL schema, see WebHookPayload for the most up-to-date payload, which is the result of the webHookEvent query.

Example:

{
  "data": {
    "webHookEvent": {
      "topic": "CLIENT_CREATE",
      "appId": "3ef22a50-072d-430c-a78f-b7646657560b",
      "accountId": "MQ==",
      "itemId": "MQ==",
      "occurredAt": "2021-08-12T16:31:36-06:00"
    }
  }
}

ℹ️ The content type for any apps created before Apr. 11, 2022 is application/x-www-form-urlencoded. The content type for any apps created after Apr. 11, 2022 is application/json.

ℹ️ The payload for any apps created before Dec. 8, 2023 will have the timestamp field named occuredAt. This field has been renamed to occurredAt for any apps created after Dec. 8, 2023.

Verify Authenticity of the Webhook

Before you respond to the webhook, you should verify that the webhook was sent from Jobber. A calculated signature is sent with every webhook, which you can use to verify the authenticity of the request.

Each request will include a base64 encoded X-Jobber-Hmac-SHA256 header, which is generated using your app's OAuth client secret and the data sent in the webhook.

Here is an example in Ruby as to how one might verify the request:

MY_SECRET = "my app's OAuth client secret"

# 'data' is the raw JSON payload
# 'hmac_header' is the base64 encoded header `X-Jobber-Hmac-SHA256`
def verify_webhook(data, hmac_header)
  digest = OpenSSL::HMAC.digest(OpenSSL::Digest.new('sha256'), MY_SECRET, data)
  calculated_hmac = Base64.strict_encode64(digest)

  # https://api.rubyonrails.org/classes/ActiveSupport/SecurityUtils.html
  # Compare strings of variable length, without exposing yourself to a timing attack
  ActiveSupport::SecurityUtils.secure_compare(calculated_hmac, hmac_header)
end

verify_webhook(
  "{\"data\":{\"webHookEvent\":{\"topic\":\"APP_CONNECT\",\"appId\":\"id\",\"accountId\":\"id\",\"itemId\":\"id\",\"occurredAt\":\"ISO8601DateTime\"}}}",
  "ks1dre6TCHsMO2GVWnDYmx3ZrxubXGbCNZ5gPiXvP9E="
)
# => returns boolean value which determines authenticity