Skip to main content

Webhooks

Webhooks allow you to listen for changes in a SavvyCal account and get notified via HTTP POST requests.

Registering a webhook

Via the user interface

Navigate to Settings > Integrations and click the + icon in the Webhooks:

The interface for registering a new webhook

Via the REST API

See the Webhooks documentation for managing webhooks via the SavvyCal API.

Consuming webhooks

When an event occurs that triggers a webhook, we will send an HTTP POST to the URL you specified, with a JSON-encoded body:

POST /my-webhook-receiver HTTP/1.1
Host: https://myapp.com
User-Agent: SavvyCal Webhooks (https://savvycal.com)
x-savvycal-signature: sha256=6CAA4DEF5C3463B785E885FF19B8987B348E19399D2C5FB291274EDFA7128105
x-savvycal-webhook-id: wh_XXXXXXXXXX
Content-Type: application/json

{
"type": "event.created",
"id": "payload_XXXXXXXXXX",
[...]
}

The URL you specify to received webhook payloads should respond quickly with a 200 OK response code. To ensure you respond in a timely manner, it's best to enqueue the body in a job queue to process asynchronously (rather than processing it in the request cycle). If we receive a non-2xx response code, we will retry with exponential backoff.

Securing webhooks

Webhooks are sent over HTTPS and are signed with a secret key. The secret key is unique to each webhook and is used to verify the authenticity of the requests. This secret can be found alongside the webhook configuration in your account's integration settings. Using the secret key, SavvyCal signs the request body with a SHA-256 HMAC signature. The signature is included in the x-savvycal-signature header.

Click here to copy your signing secret to clipboard:

Copy signing secret to clipboard

Therefore, you can verify the authenticity of a webhook request you receive by generating your own signature (with your webhook's secret key) and comparing it to the signature in the x-savvycal-signature header.

An example of how to verify the signature is shown below in Elixir:

def verify_signature(body, signature, secret) do
body = Plug.Conn.read_body(conn)
header_signature = Plug.Conn.get_req_header(conn, "x-savvycal-signature") |> List.first()

computed_signature =
:sha256
|> :crypto.hmac(secret, body)
|> Base.encode16()

# Performs a "constant time" string comparison, which helps mitigate certain
# timing attacks against regular equality operators.
Plug.Crypto.secure_compare(header_signature, computed_signature)
end

Event types

The following event types are available:

Event TypeDescription
event.createdEmitted when someone schedules a new event via one of your scheduling links (or when approval is granted by the organizer on a event that requires approval). (Schema)
event.requestedEmitted when someone schedules a new event via one of your scheduling links with approval required (see Require approval for new events). (Schema)
event.approvedEmitted when an organizer approves an event (see Require approval for new events). (Schema)
event.declinedEmitted when an organizer declines an event (see Require approval for new events). (Schema)
event.rescheduledEmitted when an existing event is rescheduled. (Schema)
event.changedEmitted when any time an event is updated, providing details about the change. (Schema)
event.canceledEmitted when an existing event is canceled. (Schema)
event.attendee.addedEmitted when a new attendee is added to an existing event (either as guest on a non-group event, or an attendee on a group event). (Schema)
event.attendee.canceledEmitted when an individual cancels from a group event. (Schema)
event.attendee.rescheduledEmitted when an individual reschedules their response to a group event. (Schema)
poll.response.createdEmitted when an individual creates a response to a poll. (Schema)
poll.response.updatedEmitted when an individual updates a response to a poll. (Schema)
workflow.action.triggeredEmitted when an action is triggered in a workflow. (Schema)