Skip to main content
Webhooks for Scheduling Links overview

Learn how webhooks work and how you can configure them via Reclaim for custom Scheduling Link workflows.

Updated over a month ago

Webhooks are automated notifications that let apps communicate when specific events happen. In the case of your Schedule Links, webhooks from Reclaim.ai enable automated workflows to be triggered when specific events occur on your booking links.

Webhook support is available on Business and Enterprise plans.

Reclaim.ai events supported by webhooks

You have a few options for webhook requests on your Reclaim Scheduling Links:

  • Scheduling Link Meeting Created

  • Scheduling Link Meeting Rescheduled

  • Scheduling Link Meeting Cancelled

Want to see webhook support for any other Reclaim.ai events? Let us know at [email protected].

Configuring webhook endpoints

Webhook endpoints can be created, edited, and deleted by the Team Admin Role. The webhook endpoints can then be used by any Team Member to send webhook requests based on events from their Scheduling Links.

To configure a new webhook endpoint:

  1. Navigate to Account Settings > Webhook Settings to manage your webhook endpoints.
    ​


    ​

  2. Click the + New button to create a new webhook configuration.
    ​

  3. Enter the webhook details:

  • Name: A descriptive name for the webhook so that it is easy to refer to.

  • Endpoint: The URL to send requests to when Reclaim.ai events occur. It must be an https URL and the hostname cannot be localhost (or a variant of localhost or an address like 127.0.0.1).

  • Secret: The shared secret that should be used to verify the authenticity of each webhook request body. Store this in a secure location and do not put it directly into your webhook handling code. Note that a secret is automatically generated upon creation but you can provide the secret and well as regenerate it.

Note: Once a webhook configuration is created you can use it to send events from specific Scheduling Links. No webhook messages will be sent until the webhook configuration is associated with at least one Scheduling Link.

To add webhooks to your Scheduling Link:

Edit a Scheduling Link that you'd like to use with webhooks and scroll down to add the webhook configuration (or multiple webhook configurations) to the Scheduling Link.

In this screenshot the webhook configuration named Example is about to be added.

Once the webhook is added to the Scheduling Link you'll see something like:

Now any time a meeting is booked, rescheduled, or cancelled for that Scheduling Link a request will be sent to the endpoint.

Custom data

It is possible to pass custom data through Scheduling Link booking links by including up to five data--prefixed query parameters on the booking link URL. The total size of custom data when represented as a minified JSON string must be less than 512 bytes.

The custom data query parameters (both the key and value) are passed along to the webhook receiver in the payload. For example, when https://app.reclaim.ai/m/some-scheduling-link/example-meeting?data-mydata=12345 is used to book a meeting, the webhook payload will include an object at the key custom_data with this structure:
​

"custom_data": {
"data": {
"mydata": "12345"
},
"errors": []
}

Note that the data- prefix is removed from the key in the data object.

The errors array will include any errors that occurred while processing on the server. Potential errors are:

  • TRUNCATED indicates that the custom data was too large: either there were greater than five key/value pairs or the total data size as minified JSON was greater than 512 bytes. The data object will be empty in this case.

  • UNSERIALIZABLE indicates that at least on key or value was not serializable to JSON. The data object will be empty in this case.

Handling webhook requests

Webhook requests from Reclaim.ai:

  • Use the POST HTTP method

  • Only HTTPS is supported.

  • Will not be sent to hosts like localhost nor IP addresses like 127.0.0.1

  • Use application/json as the content-type

  • Include these additional headers:

    • x-reclaim-webhook-type - the type of Reclaim.ai event that cause the webhook request to be made. Valid values are:

      • SchedulingLink.Meeting.Created - when a new meeting is booked for a Scheduling Link.

      • SchedulingLink.Meeting.Updated - when an existing Scheduling Link meeting is updated, usually because it was rescheduled.

      • SchedulingLink.Meeting.Cancelled - when an existing Scheduling Link meeting is cancelled.

    • x-reclaim-api-version - the version of the Reclaim.ai webhook API the request used. Reclaim.ai webhooks use a date-based versioning scheme. Currently there is only one version: v2024-10-02.

    • x-reclaim-signature-256 - the cryptographic signature calculated from the raw body of the request and the secret from the webhook configuration. See below for an example using NodeJS for validating the body using the secret and the value of this header.

  • Will attempt At Most Once successful delivery but may, in rare cases, be delivered successfully more than once. Successful delivery is defined as the webhook receiver responding with HTTP statuses 200, 201, 202, or 204 within 10 seconds. Reclaim.ai will retry delivery if there is a failure using an exponential backoff approach in the absence of a Retry-After header in the response. If requests to the webhook endpoint fail for more than 24 hours without success the webhook configuration is put into a suspended state where new requests are not sent to the endpoint.

  • Will not necessarily be delivered in event occurrence order, although an effort is made for deliver in order of event occurrence and is the expected usual case. For example, this property means that it is possible to receive a webhook request for a Scheduling Link Meeting being rescheduled before receiving the webhook request for the creation event for the same meeting.

SchedulingLink.Meeting.Created body

Here's an example of the HTTP request body sent when a new meeting is booked with a Scheduling Link.

{
"type": "SchedulingLink.Meeting.Created",
"event_ts": "2024-10-17T21:28:08.140Z",
"webhook_sent_at": "2024-10-17T21:28:09.676440Z",
"api_version": "v2024-10-02",
"request_id": "01929c61-84d5-7c1c-9374-5eaf5e317114",
"meeting": {
"participants": [
{
"is_host": true,
"user_id": "GUID FOR THE PARTICIPANT USER ID"
}
],
"attendee": {
"attendee_id": "GUID FOR THE ATTENDEE USER ID, IF ANY",
"attendee_email": "ATTENDEE EMAIL ADDRESS",
"attendee_name": "ATTENDEE NAME",
"attendee_zone_id": {
"id": "America/Los_Angeles",
"display_name": "Pacific Time",
"abbreviation": "PT"
}
},
"start_time": "2024-10-22T17:30:00Z",
"end_time": "2024-10-22T17:45:00Z",
"scheduled_at": "2024-10-17T21:28:08.14Z",
"scheduling_link_id": "GUID FOR THE SCHEDULING LINK ID",
"meeting_id": "MEETING ID",
"ccs": "",
"message": "",
"meeting_location": {
"conference_location": "GOOGLE_MEET"
},
"survey": {
"survey_items": []
},
"custom_data": {
"data": {
"mydata": "12345"
},
"errors": []
}
},
"webhook_configuration_id": "GUID FOR THE WEBHOOK CONFIGURATION ID"
}

SchedulingLink.Meeting.Updated body

Here's an example of the HTTP request body sent when an existing Scheduling Link meeting is rescheduled.

The previous_meeting object holds the meeting information from before the meeting changed. The new_meeting object holds the meeting information from after the meeting changed.

{
"type": "SchedulingLink.Meeting.Updated",
"event_ts": "2024-10-17T21:28:08.140Z",
"webhook_sent_at": "2024-10-17T22:59:54.479914Z",
"api_version": "v2024-10-02",
"request_id": "01929cb5-83f0-77a3-b67a-2be00ae45c6e",
"previous_meeting": {
"participants": [
{
"is_host": true,
"user_id": "GUID FOR THE PARTICIPANT USER ID"
}
],
"attendee": {
"attendee_id": "GUID FOR THE ATTENDEE USER ID, IF ANY",
"attendee_email": "ATTENDEE EMAIL ADDRESS",
"attendee_name": "ATTENDEE NAME",
"attendee_zone_id": {
"id": "America/Los_Angeles",
"display_name": "Pacific Time",
"abbreviation": "PT"
}
},
"start_time": "2024-10-18T21:00:00Z",
"end_time": "2024-10-18T21:15:00Z",
"scheduled_at": "2024-10-17T21:28:08.14Z",
"rescheduled_at": "2024-10-17T22:59:52.815Z",
"scheduling_link_id": "GUID FOR THE SCHEDULING LINK ID",
"meeting_id": "MEETING ID",
"ccs": "",
"message": "",
"meeting_location": {
"conference_location": "GOOGLE_MEET"
},
"survey": {
"survey_items": []
},
"custom_data": {
"data": {
"mydata": "12345"
},
"errors": []
}
},
"new_meeting": {
"participants": [
{
"is_host": true,
"user_id": "GUID FOR THE PARTICIPANT USER ID"
}
],
"attendee": {
"attendee_id": "GUID FOR THE ATTENDEE USER ID, IF ANY",
"attendee_email": "ATTENDEE EMAIL ADDRESS",
"attendee_name": "ATTENDEE NAME",
"attendee_zone_id": {
"id": "America/Los_Angeles",
"display_name": "Pacific Time",
"abbreviation": "PT"
}
},
"start_time": "2024-10-18T21:00:00Z",
"end_time": "2024-10-18T21:15:00Z",
"scheduled_at": "2024-10-17T21:28:08.14Z",
"rescheduled_at": "2024-10-17T22:59:52.815Z",
"scheduling_link_id": "GUID FOR THE SCHEDULING LINK ID",
"meeting_id": "MEETING ID",
"ccs": "",
"message": "",
"meeting_location": {
"conference_location": "GOOGLE_MEET"
},
"survey": {
"survey_items": []
},
"custom_data": {
"data": {
"mydata": "12345"
},
"errors": []
}
},
"change_reason": "",
"webhook_configuration_id": "GUID FOR THE WEBHOOK CONFIGURATION ID"
}

SchedulingLink.Meeting.Cancelled body

Here's an example of the HTTP request body sent when an existing meeting is cancelled.

{
"type": "SchedulingLink.Meeting.Cancelled",
"event_ts": "2024-10-18T00:01:14.327Z",
"webhook_sent_at": "2024-10-18T00:01:16.130377Z",
"api_version": "v2024-10-02",
"request_id": "01929ced-b162-77aa-9d2b-2f8e042d67d7",
"meeting": {
"participants": [
{
"is_host": true,
"user_id": "GUID FOR THE PARTICIPANT USER ID"
}
],
"attendee": {
"attendee_id": "GUID FOR THE ATTENDEE USER ID, IF ANY",
"attendee_email": "ATTENDEE EMAIL ADDRESS",
"attendee_name": "ATTENDEE NAME",
"attendee_zone_id": {
"id": "America/Los_Angeles",
"display_name": "Pacific Time",
"abbreviation": "PT"
}
},
"start_time": "2024-10-18T21:00:00Z",
"end_time": "2024-10-18T21:15:00Z",
"scheduled_at": "2024-10-17T21:28:08.14Z",
"rescheduled_at": "2024-10-17T22:59:54.08151Z",
"cancelled_at": "2024-10-18T00:01:14.327Z",
"scheduling_link_id": "GUID FOR THE SCHEDULING LINK ID",
"meeting_id": "MEETING ID",
"ccs": "",
"message": "",
"meeting_location": {
"conference_location": "GOOGLE_MEET"
},
"survey": {
"survey_items": []
},
"custom_data": {
"data": {
"mydata": "12345"
},
"errors": []
}
},
"webhook_configuration_id": "GUID FOR THE WEBHOOK CONFIGURATION ID"
}

Verifying the authenticity of the webhook body

Reclaim.ai webhook requests provide an HMAC signature in the x-reclaim-signature-256 header so that the receiver can verify the authenticity of the request and body. The value in that header has the prefix sha256= followed by the cryptographic signature calculated from the raw body of the request and the secret configured for the webhook. The digest uses base64 encoding.

NodeJS example

This Javascript function verifies that the body is authentic given the HTTP headers, body, and secret using Node's Crypto library.

import crypto from 'crypto';

// Validate the body with the secret + signature from the `x-reclaim-signature-256` header
// `headers` is the set of headers from the HTTP request, e.g. `request.headers`
// `body` is the raw body from the HTTP request
// `secret` is the secret from the webhook configuration - store it in a secure location, not in your code
function signatureMatchesBody(headers, body, secret) {
const SIGNATURE_PREFIX_LENGTH = 7;
const signatureWithPrefix = headers['x-reclaim-signature-256'];
// Check that the signature exists from the request header
if (signatureWithPrefix == null) {
// No signature in the request headers
return false;
}

const signature = Buffer.from(signatureWithPrefix.substring(SIGNATURE_PREFIX_LENGTH));
const hmac = crypto.createHmac('sha256', secret)
hmac.update(body);
const hash = Buffer.from(hmac.digest('base64'));

return signature.length === hash.length && crypto.timingSafeEqual(signature, hash);
}

Did this answer your question?