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.
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.
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.
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
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 isapplication/json
.
ℹ️ The payload for any apps created before Dec. 8, 2023 will have the timestamp field named
occuredAt
. This field has been renamed tooccurredAt
for any apps created after Dec. 8, 2023.
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