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

# Webhook Integration

Parchment Health uses **webhooks** to notify your application in real-time when events happen in your account. This guide will help you integrate with our webhook system to receive and process event notifications.

***

## Overview

* **Delivery**: HTTP POST requests to your configured endpoint
* **Retry Logic**: Up to 3 attempts with exponential backoff (see [Retry Behavior](#retry-behavior))
* **Request Timeout**: 10 seconds per attempt
* **Supported Events**: Prescription lifecycle events

***

## How Webhooks Work

1. An event occurs in Parchment (e.g., a prescription is created)
2. Parchment sends an HTTPS POST request to your webhook endpoint
3. Your application processes the event
4. Your endpoint returns a 2xx status code to confirm receipt

***

## How to Activate Webhooks

<Steps>
  <Step title="Provide your webhook endpoint">
    Contact Parchment support at [hello@parchment.health](mailto:hello@parchment.health) with:

    * Your **webhook endpoint URL** (must be HTTPS)
    * Your **partner ID**

    ```bash theme={null}
    # Example
    Partner ID: yourpartner
    Webhook URL: https://api.yourapp.com/webhooks/parchment
    ```
  </Step>

  <Step title="Receive your webhook secret">
    Parchment registers your endpoint and provides a confirmation with your **webhook secret**. This secret is shown **only once** — store it securely.

    ```json theme={null}
    {
      "partner_id": "demo",
      "webhook_url": "https://api.yourapp.com/webhooks/parchment",
      "webhook_secret": "whsec_a1b2c3d4e5f6...",
      "message": "Webhook registered successfully. Store this secret securely - it will not be shown again."
    }
    ```

    <Warning>
      Save the `webhook_secret` immediately. It cannot be retrieved again. If lost, contact Parchment support to rotate it.
    </Warning>
  </Step>

  <Step title="Implement your webhook handler">
    Build an endpoint that verifies the signature and routes events by type. Here's a complete Node.js example:

    ```typescript theme={null}
    import express from 'express';
    import crypto from 'crypto';

    const app = express();

    // Retrieve from your secrets manager (e.g. AWS Secrets Manager, HashiCorp Vault)
    let WEBHOOK_SECRET: string;

    async function start() {
      WEBHOOK_SECRET = await getSecret('parchment-webhook-secret');
      app.listen(3000);
    }

    app.post('/webhooks/parchment',
      express.raw({ type: 'application/json' }),
      (req, res) => {
        const signature = req.headers['x-webhook-signature'] as string;
        const payload = req.body.toString('utf8');

        // Step 1: Verify the signature
        const result = verifyWebhook(payload, signature, WEBHOOK_SECRET);
        if (!result.valid) {
          console.error('Signature verification failed:', result.error);
          return res.status(400).json({ error: 'Invalid signature' });
        }

        // Step 2: Parse and route by event type
        const event = JSON.parse(payload);

        switch (event.event_type) {
          case 'prescription.created':
            handlePrescriptionCreated(event);
            break;
          default:
            console.log('Unhandled event type:', event.event_type);
        }

        // Step 3: Acknowledge receipt
        res.status(200).json({ received: true });
      }
    );

    function verifyWebhook(
      payload: string,
      signatureHeader: string,
      secret: string,
      toleranceSeconds = 300
    ): { valid: boolean; error?: string } {
      const parts = signatureHeader.split(',');
      let timestamp: number | null = null;
      let signature: string | null = null;

      for (const part of parts) {
        const [key, value] = part.split('=');
        if (key === 't') timestamp = parseInt(value, 10);
        else if (key === 'v1') signature = value;
      }

      if (!timestamp || !signature || isNaN(timestamp)) {
        return { valid: false, error: 'Invalid signature header format' };
      }

      // Check timestamp tolerance (prevents replay attacks)
      if (Math.abs(Math.floor(Date.now() / 1000) - timestamp) > toleranceSeconds) {
        return { valid: false, error: 'Timestamp expired' };
      }

      // Compute expected signature
      const expected = crypto
        .createHmac('sha256', secret)
        .update(`${timestamp}.${payload}`)
        .digest('hex');

      // Timing-safe comparison
      const isValid = crypto.timingSafeEqual(
        Buffer.from(signature),
        Buffer.from(expected)
      );

      return isValid ? { valid: true } : { valid: false, error: 'Signature mismatch' };
    }

    function handlePrescriptionCreated(event: any) {
      console.log('New prescription:', event.data.scid);
      // Fetch full details via API using the SCID
    }

    start();
    ```

    See [Webhook Signature Verification](/webhooks/webhook-verification) for Python examples and more details.
  </Step>

  <Step title="Test your integration">
    Once deployed, verify that:

    * Your endpoint returns a `2xx` status code
    * Signature verification passes
    * Events are routed correctly
  </Step>
</Steps>

***

## Webhook Endpoint Requirements

Your webhook endpoint must:

* **Use HTTPS** with a valid SSL certificate
* **Return a `2xx` response within 10 seconds** — process events asynchronously if needed
* **Handle retries idempotently** — use the `event_id` to prevent duplicate processing

***

## Request Headers

Every webhook request includes the following headers:

| Header                | Value                            | Description                                                                                                  |
| --------------------- | -------------------------------- | ------------------------------------------------------------------------------------------------------------ |
| `Content-Type`        | `application/json`               | Payload is always JSON                                                                                       |
| `X-Webhook-Signature` | `t=<timestamp>,v1=<hmac-sha256>` | Signature used to verify authenticity — see [Webhook Signature Verification](/webhooks/webhook-verification) |
| `User-Agent`          | `Parchment-Webhooks/1.0`         | Identifies requests originating from Parchment                                                               |

***

## Retry Behavior

Parchment automatically retries failed webhook deliveries using **exponential backoff**.

### Retry Schedule

Each event is attempted up to **3 times**. A request is considered failed if it does not return a `2xx` status code within the 10-second timeout, or if a network error occurs.

| Attempt | Delay Before Attempt            | Cumulative Time |
| ------- | ------------------------------- | --------------- |
| 1       | Immediate                       | 0s              |
| 2       | 1 second after attempt 1 fails  | \~1s            |
| 3       | 2 seconds after attempt 2 fails | \~3s            |

After the 3rd attempt fails, the event is **not retried further** and is logged as permanently failed on Parchment's side.

### Retryable vs. Non-Retryable Responses

Parchment distinguishes between transient and permanent failures to avoid pointless retries:

**Retryable** (will retry up to the limit):

* Network errors (connection refused, DNS failure, TLS errors)
* Request timeouts (no response within 10 seconds)
* `408 Request Timeout`
* `429 Too Many Requests`
* Any `5xx` server error

**Non-retryable** (fails immediately after the first attempt):

* Any other `4xx` client error (e.g. `400`, `401`, `403`, `404`)

<Note>
  A `4xx` response (other than `408` / `429`) signals a problem with your endpoint configuration — bad signature handling, wrong auth, missing route — that retrying won't fix. Fix the endpoint and contact Parchment support if you need the event re-sent.
</Note>

### When Retries Stop

Parchment stops retrying a webhook event as soon as **any one** of the following occurs:

1. **Successful delivery** — your endpoint returns a `2xx` status code.
2. **Non-retryable response** — your endpoint returns a `4xx` other than `408` or `429`. The event fails immediately with no further attempts.
3. **Retry budget exhausted** — all 3 attempts have failed with retryable errors.

Once retries stop, the event is **not re-sent automatically**. Failures are logged on Parchment's side; contact [hello@parchment.health](mailto:hello@parchment.health) if you need a failed event replayed.

### Idempotency

Because retries can occasionally result in duplicate deliveries (e.g. if your endpoint returned a non-2xx status but still processed the event), your handler **must be idempotent**. Use the top-level `event.id` field from the payload as an idempotency key and skip any event ID you have already processed.

### Rate Limiting

If your endpoint needs to throttle Parchment, return `429 Too Many Requests`. Parchment will back off and retry according to the schedule above.

***

## Event Types

Parchment sends webhooks for these event types:

| Event Type               | Description                    | Status      |
| ------------------------ | ------------------------------ | ----------- |
| `prescription.created`   | A new prescription was created | Live        |
| `prescription.ceased`    | A prescription was ceased      | Live        |
| `prescription.cancelled` | A prescription was cancelled   | Live        |
| `prescription.reissued`  | A prescription was reissued    | Live        |
| `prescription.updated`   | A prescription was modified    | Coming Soon |

See [Webhook Events](/webhooks/webhook-events) for detailed payload examples.
