# Webhooks (/developers/webhooks)

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

## Registering a webhook [#registering-a-webhook]

### Via the user interface [#via-the-user-interface]

Navigate to **Settings** > [Integrations](https://savvycal.com/integrations&#x29; and click the &#x2A;*+** icon in the Webhooks:

![The interface for registering a new webhook](/images/developers/create-webhook.webp)

### Via the REST API [#via-the-rest-api]

See the [Webhooks](/developers/v1/api/create-webhook) documentation for managing webhooks via the SavvyCal API.

## Consuming webhooks [#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:

```http
POST /my-webhook-receiver HTTP/1.1
Host: 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 receive webhook payloads should respond quickly with a `2xx` response code, such as `200 OK`. 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). SavvyCal follows up to 3 redirects. If we receive a non-2xx response code, or can't connect, we will retry with exponential backoff, up to 20 attempts in all. To see each attempt with its request payload and your response, use the webhook's [request log](/webhooks#logs).

## Securing webhooks [#securing-webhooks]

Webhooks are signed with a secret key. Use an `https://` URL so payloads are encrypted in transit; `http://` URLs are accepted but not recommended. 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, where you can also [regenerate it](/webhooks#regenerate-secret). 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](/images/developers/webhook-secret.webp)

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:

```elixir
# `body` is the raw request body, and `signature` is the value of the
# `x-savvycal-signature` header (for example, "sha256=6CAA4DEF...").
def verify_signature(body, signature, secret) do
  computed_signature =
    "sha256=" <> Base.encode16(:crypto.mac(:hmac, :sha256, secret, body))

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

## Event types [#event-types]

The following event types are available:

| Event Type                   | Description                                                                                                                                                                                                                                |
| ---------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `event.created`              | Emitted 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](/developers/v1/api/schemas/eventcreatedwebhook))                   |
| `event.requested`            | Emitted when someone schedules a new event via one of your scheduling links with approval required (see [Require approval for new events](/require-approval-for-new-events)). ([Schema](/developers/v1/api/schemas/eventrequestedwebhook)) |
| `event.approved`             | Emitted when an organizer approves an event (see [Require approval for new events](/require-approval-for-new-events)). ([Schema](/developers/v1/api/schemas/eventapprovedwebhook))                                                         |
| `event.declined`             | Emitted when an organizer declines an event (see [Require approval for new events](/require-approval-for-new-events)). ([Schema](/developers/v1/api/schemas/eventdeclinedwebhook))                                                         |
| `event.rescheduled`          | Emitted when an existing event is rescheduled. ([Schema](/developers/v1/api/schemas/eventrescheduledwebhook))                                                                                                                              |
| `event.changed`              | Emitted when any time an event is updated, providing details about the change. ([Schema](/developers/v1/api/schemas/eventchangedwebhook))                                                                                                  |
| `event.canceled`             | Emitted when an existing event is canceled. ([Schema](/developers/v1/api/schemas/eventcanceledwebhook))                                                                                                                                    |
| `event.checkout.pending`     | Emitted when someone books a paid event, but is still pending checkout. ([Schema](/developers/v1/api/schemas/eventcheckoutpendingwebhook))                                                                                                 |
| `event.checkout.expired`     | Emitted when someone books a paid event, without paying and checkout expires. ([Schema](/developers/v1/api/schemas/eventcheckoutexpiredwebhook))                                                                                           |
| `event.checkout.completed`   | Emitted when someone books a paid event, after successful checkout. ([Schema](/developers/v1/api/schemas/eventcheckoutcompletedwebhook))                                                                                                   |
| `event.attendee.added`       | Emitted 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](/developers/v1/api/schemas/eventattendeeaddedwebhook))                                        |
| `event.attendee.canceled`    | Emitted when an individual cancels from a group event. ([Schema](/developers/v1/api/schemas/eventattendeecanceledwebhook))                                                                                                                 |
| `event.attendee.rescheduled` | Emitted when an individual reschedules their response to a group event. ([Schema](/developers/v1/api/schemas/eventattendeerescheduledwebhook))                                                                                             |
| `poll.response.created`      | Emitted when an individual creates a response to a poll. ([Schema](/developers/v1/api/schemas/pollresponsecreatedwebhook))                                                                                                                 |
| `poll.response.updated`      | Emitted when an individual updates a response to a poll. ([Schema](/developers/v1/api/schemas/pollresponseupdatedwebhook))                                                                                                                 |
| `workflow.action.triggered`  | Emitted when an action is triggered in a workflow. ([Schema](/developers/v1/api/schemas/workflowactiontriggeredwebhook))                                                                                                                   |
