> ## Documentation Index
> Fetch the complete documentation index at: https://api-docs.scholarlysoftware.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Webhooks

> Webhooks are great for listening and responding to changes within Scholarly

## Overview

Scholarly provides webhooks to notify your application when events occur in the system. Webhooks are HTTP POST requests sent to a URL you specify, containing information about the event that occurred.

Webhooks are a great way to received information about changes in the Scholarly system near-instantaneously. This can be an important tool for
keeping other tools or platforms in sync with Scholarly's data.

Scholarly webhooks adhere to the [Standard Webhooks specification](https://www.standardwebhooks.com/).

Webhooks can be created and configured by navigating to [Settings > Webhooks](https://app.scholarlysoftware.com/institution/settings/webhooks) in the Scholarly platform.

## Authentication

All webhook requests include a signature in the `X-Webhook-Signature` header that you can use to verify the request came from Scholarly. The signature is an HMAC-SHA256 hash of the request body using your webhook's secret key.

### Verifying Webhook Signatures

<CodeGroup>
  ```ruby verify_webhook_signature.rb theme={null}
  # Example webhook verification in Ruby
  def verify_webhook_signature(request)
    signature = request.headers['X-Webhook-Signature']
    body = request.body.read

    expected_signature = "sha256=#{OpenSSL::HMAC.hexdigest('SHA256', webhook_secret, body)}"

    Rack::Utils.secure_compare(signature, expected_signature)
  end
  ```

  ```javascript verifyWebhookSignature.js theme={null}
  // Example webhook verification in Node.js
  const crypto = require('crypto');

  function verifyWebhookSignature(req, webhookSecret) {
    const signature = req.headers['x-webhook-signature'];
    const body = JSON.stringify(req.body);

    const expectedSignature = 'sha256=' + crypto
      .createHmac('sha256', webhookSecret)
      .update(body)
      .digest('hex');

    return signature === expectedSignature;
  }
  ```
</CodeGroup>

## Webhook Headers

All webhook requests include the following headers:

| Header                | Description                                    |
| --------------------- | ---------------------------------------------- |
| `Content-Type`        | Always `application/json`                      |
| `User-Agent`          | `Scholarly-Webhook/1.0`                        |
| `X-Webhook-Id`        | Unique identifier of the webhook configuration |
| `X-Webhook-Event`     | The type of event (e.g., `department.created`) |
| `X-Webhook-Delivery`  | Unique identifier for this delivery attempt    |
| `X-Webhook-Timestamp` | Unix timestamp of when the webhook was sent    |
| `X-Webhook-Signature` | HMAC-SHA256 signature of the request body      |

## Webhook Payload Format

All webhooks use a "thin" payload format that includes minimal information about the event. Your application should use the provided ID to fetch the full resource data via the appropriate API endpoint if needed.

```json theme={null}
{
  "type": "event.type",
  "timestamp": "2025-01-20T10:30:00Z",
  "data": {
    "id": "resource-uuid"
  }
}
```

## Event Types

Please see the `API Reference > Webhooks` section in the side bar for a list of all events and their structure.

## Retry Behavior

If your webhook endpoint doesn't respond with a 2xx status code, Scholarly will retry the webhook with exponential backoff:

* **Maximum retries:** 5 attempts
* **Retry schedule:**
  * 1st retry: 10 seconds
  * 2nd retry: 20 seconds
  * 3rd retry: 40 seconds
  * 4th retry: 80 seconds (1.3 minutes)
  * 5th retry: 160 seconds (2.7 minutes, capped at 1 hour)

After 5 failed attempts, the webhook delivery will be marked as failed and no further retries will be attempted.

## Response Requirements

Your webhook endpoint should:

1. **Respond quickly** - Process the webhook asynchronously if needed. We recommend responding within 30 seconds.
2. **Return 2xx status** - Any 2xx status code (200-299) indicates successful receipt.
3. **Be idempotent** - The same webhook may be delivered multiple times due to retries.

## Example Webhook Handler

<CodeGroup>
  ```ruby webhooks_controller.rb theme={null}
  # Ruby/Rails example
  class WebhooksController < ApplicationController
    skip_before_action :verify_authenticity_token

    def handle
      # Verify the webhook signature
      unless verify_webhook_signature(request)
        head :unauthorized
        return
      end

      # Parse the webhook payload
      event = JSON.parse(request.body.read)

      # Process the webhook asynchronously
      ProcessWebhookJob.perform_later(event)

      # Respond immediately
      head :ok
    end

    private

    def verify_webhook_signature(request)
      signature = request.headers['X-Webhook-Signature']
      body = request.body.read
      request.body.rewind # Reset for re-reading

      expected = "sha256=#{OpenSSL::HMAC.hexdigest('SHA256', ENV['SCHOLARLY_WEBHOOK_SECRET'], body)}"

      Rack::Utils.secure_compare(signature, expected)
    end
  end
  ```

  ```javascript server.js theme={null}
  // Node.js/Express example
  const express = require('express');
  const crypto = require('crypto');

  app.post('/webhooks', express.raw({type: 'application/json'}), (req, res) => {
    // Verify the webhook signature
    const signature = req.headers['x-webhook-signature'];
    const expectedSignature = 'sha256=' + crypto
      .createHmac('sha256', process.env.SCHOLARLY_WEBHOOK_SECRET)
      .update(req.body)
      .digest('hex');

    if (signature !== expectedSignature) {
      return res.status(401).send('Unauthorized');
    }

    // Parse the webhook payload
    const event = JSON.parse(req.body);

    // Process the webhook asynchronously
    processWebhook(event);

    // Respond immediately
    res.status(200).send('OK');
  });

  async function processWebhook(event) {
    switch(event.type) {
      case 'department.created':
        // Handle department creation
        const departmentId = event.data.id;
        // Fetch full department data from API if needed
        break;
      case 'profile.created':
      case 'profile.updated':
      case 'profile.deleted':
        // Handle profile events (created, updated, deleted)
        const profileId = event.data.id;
        // Note: profile.updated includes avatar changes
        // Fetch full profile data from API if needed
        break;
      case 'primary_appointment.created':
      case 'primary_appointment.updated':
      case 'primary_appointment.deleted':
        // Handle primary appointment events (created, updated, deleted)
        const primaryAppointmentId = event.data.id;
        // Fetch full primary appointment data from API if needed
        break;
      case 'additional_appointment.created':
      case 'additional_appointment.updated':
      case 'additional_appointment.deleted':
        // Handle secondary appointment events (created, updated, deleted)
        const secondaryAppointmentId = event.data.id;
        // Fetch full secondary appointment data from API if needed
        break;
      case 'user.created':
      case 'user.updated':
      case 'user.deleted':
        // Handle user events (created, updated, deleted)
        const userId = event.data.id;
        // Note: user.updated includes changes to the user's custom fields
        // Fetch full user data from API if needed
        break;
      // Handle other event types...
    }
  }
  ```
</CodeGroup>

## Rate Limits

* Maximum of 100 concurrent webhook deliveries per institution
* No limit on the number of webhooks per institution
* No limit on the number of events per webhook

## Best Practices

1. **Use HTTPS** - Always use HTTPS endpoints for webhooks to ensure data security.
2. **Verify signatures** - Always verify webhook signatures to ensure authenticity.
3. **Handle duplicates** - Design your system to handle duplicate webhook deliveries idempotently.
4. **Process asynchronously** - Queue webhook processing to respond quickly.
5. **Monitor failures** - Set up monitoring for failed webhook deliveries.
6. **Use thin payloads** - Fetch full resource data via API when needed rather than relying on webhook payload.

## Testing Webhooks

During development, you can use tools like:

* [ngrok](https://ngrok.com/) - Expose your local server to receive webhooks
* [webhook.site](https://webhook.site/) - Inspect webhook payloads
* [RequestBin](https://requestbin.com/) - Create temporary webhook endpoints for testing

## Support

For questions about webhooks or to report issues, please contact [support@scholarlysoftware.com](mailto:support@scholarlysoftware.com) or open an issue in your customer support channel.
