# Webhooks

Webhooks notify a URL when an appointment is scheduled, canceled, or rescheduled. Acuity can also send webhooks when packages/gift certificates/subscription orders are completed.

You can [create static webhooks](https://developers.acuityscheduling.com/docs/webhooks#section-static-webhooks) in your Acuity account settings, or [create webhooks dynamically using our API](https://developers.acuityscheduling.com/docs/webhooks#section-dynamic-webhooks).

## Static Webhooks

Under [Integrations](https://secure.acuityscheduling.com/preferences.php?action=integrations#api) set the Webhook integration with the URL to be notified on changes to appointments. The URL you provide must be accessible by port 443 (for HTTPS) or 80(for HTTP).

Webhook requests are `application/x-www-form-urlencoded` POST requests with these variables:

* <b>action</b>

  * **For appointments:**

    * `scheduled` is called once when an appointment is initially booked
    * `rescheduled` is called when the appointment is rescheduled to a new time
    * `canceled` is called whenever an appointment is canceled
    * `changed` is a catch-all webhook called when the appointment is changed in any way

      * This includes when it is initially scheduled, rescheduled, or canceled, as well as when appointment details such as e-mail address or intake forms are updated. Using the `changed` webhook concurrently with the `scheduled`, `rescheduled` or `canceled` webhooks may lead to duplicate requests from Acuity.
  * **For Packages, Gift Certificates and Subscription orders:**
    * `order.completed` is called when an order is completed
* <b>id</b> the ID for the appointment or order
  * Get the details through the [get appointment by ID](https://developers.acuityscheduling.com/reference/get-appointments-id) or the [get orders](https://developers.acuityscheduling.com/reference/ordersid) API call
* <b>calendarID</b> the ID of the calendar for the appointment.
  * *Note: This is only sent for appointments.*
* <b>appointmentTypeID</b> the ID of the type of the appointment.
  * *Note: This is only sent for appointments*

## Dynamic Webhooks

This new feature allows you to create new Webhook subscriptions through the Webhooks API. [Click here to learn all details.](https://developers.acuityscheduling.com/page/webhooks-webhooks-webhooks)

## Verifying Webhook Requests

Webhook notifications are signed by Acuity using the main admin's API key for **static** webhooks or your user account's API key for **dynamic** webhooks.  You can use this signature to verify that a notification is from Acuity.  First compute the base64 `HMAC-SHA256` signature of the notification using the request's body as the message and your API key as the shared secret.  Then compare this signature to the request header `x-acuity-signature` .  If they match, the notification is authentic.

```php
<?php
// Get hash of message using shared secret:
$body = file_get_contents('php://input');
$hash = base64_encode(hash_hmac('sha256', $body, $secret, true));

// Compare the two:
if ($hash !== $_SERVER['HTTP_X_ACUITY_SIGNATURE']) {
	throw new Exception('This message was forged!');
}
```

```javascript
// Get hash of message using shared secret:
var hasher = crypto.createHmac('sha256', secret);
hasher.update(buf.toString());
var hash = hasher.digest('base64');

// Compare hash to Acuity signature:
if (hash !== req.header('x-acuity-signature')) {
	throw new Error('This message was forged!');
}
```

```ruby
require 'openssl'
require 'base64'

def verify_message_signature(secret, body, signature)
  hash = Base64.encode64(OpenSSL::HMAC.digest(OpenSSL::Digest.new('sha256'), secret, body))
  if hash.strip() != signature
    raise 'This message was forged!'
  end
end
```

```http Example Request
POST https://example.com/webhook-callback

action=changed&id=13&calendarID=1&appointmentTypeID=13
```

> 👍 Simulate Acuity Webhooks
>
> You can use `curl` to simulate Acuity webhooks:
>
> ```
> curl -d "action=changed&id=1&calendarID=1&appointmentTypeID=1" "https://example.com/webhook-callback"
> ```

## Webhook Retries

Webhook calls are retried with exponential backoff over a 24 hour period. Acuity will stop attempting if there is not a successful response after 24 hours. We only retry webhooks if they return a `500` internal server error, or if we experience network related connection errors when attempting to send the webhook.

| Retry Number | Interval   |
| :----------- | :--------- |
| 1            | 2 seconds  |
| 2            | 30 seconds |
| 3            | 1 minute   |
| 4            | 5 minutes  |
| 5            | 10 minutes |
| 6            | 15 minutes |
| 7            | 30 minutes |
| 8            | 1 hour     |
| 9            | 12 hours   |

**NOTE** If a webhook continues to fail because of a `500` HTTP error or because of connection issues over a period of 5 days, we will *disable* the webhook. It will have to be manually re-enabled by you, either through the API for API webhooks or through the Integrations page for static webhooks.