Skip to main content

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.

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)
  • 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

1

Provide your webhook endpoint

Contact Parchment support at hello@parchment.health with:
  • Your webhook endpoint URL (must be HTTPS)
  • Your partner ID
# Example
Partner ID: yourpartner
Webhook URL: https://api.yourapp.com/webhooks/parchment
2

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.
{
  "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."
}
Save the webhook_secret immediately. It cannot be retrieved again. If lost, contact Parchment support to rotate it.
3

Implement your webhook handler

Build an endpoint that verifies the signature and routes events by type. Here’s a complete Node.js example:
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 for Python examples and more details.
4

Test your integration

Once deployed, verify that:
  • Your endpoint returns a 2xx status code
  • Signature verification passes
  • Events are routed correctly

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:
HeaderValueDescription
Content-Typeapplication/jsonPayload is always JSON
X-Webhook-Signaturet=<timestamp>,v1=<hmac-sha256>Signature used to verify authenticity — see Webhook Signature Verification
User-AgentParchment-Webhooks/1.0Identifies 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.
AttemptDelay Before AttemptCumulative Time
1Immediate0s
21 second after attempt 1 fails~1s
32 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)
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.

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 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 TypeDescriptionStatus
prescription.createdA new prescription was createdLive
prescription.updatedA prescription was modifiedComing Soon
prescription.ceasedA prescription was ceasedComing Soon
prescription.reissuedA prescription was reissuedComing Soon
See Webhook Events for detailed payload examples.