Agent skill

athenahealth Webhooks Skill

Receive and verify athenahealth webhooks (Event Subscription Platform, FHIR Subscription notifications). Use when setting up athenahealth webhook handlers, debugging X-Hub-Signature verification, or handling topics like Appointment.schedule, Appointment.cancel, Encounter.signoff, and LabResult.create.

Install this skill

npx skills add hookdeck/webhook-skills --skill athenahealth-webhooks


When to Use This Skill

  • How do I receive athenahealth webhooks (FHIR Subscription notifications)?
  • How do I verify the X-Hub-Signature header from athenahealth?
  • Why is my athenahealth webhook signature verification failing?
  • How do I handle Appointment.schedule, Appointment.cancel, Encounter.signoff, or LabResult.create events?
  • How do I read the subscription notification Bundle and fetch the changed resource?

Verification (core)

athenahealth signs the raw request body using WebSub content signing: HMAC-SHA256 keyed with the X-Hub-Secret value you chose when you created the FHIR Subscription. The hex digest arrives in X-Hub-Signature as sha256=<hex>. athenahealth says the method may change from sha256 in future, so check the method in the header. Only sha256 is accepted here: a future method change would fail verification until you add support for it.

Node:

const crypto = require('crypto');

function verify(rawBody, signatureHeader, secret) {
  if (!signatureHeader || !secret || !signatureHeader.startsWith('sha256=')) return false;
  const sig = signatureHeader.slice('sha256='.length);
  if (!/^[0-9a-f]{64}$/i.test(sig)) return false; // check before comparing: bad input is rejected, never truncated
  const expected = crypto.createHmac('sha256', secret).update(rawBody).digest('hex');
  // Both are 64 ASCII hex chars, so the Buffers have equal length
  return crypto.timingSafeEqual(Buffer.from(sig.toLowerCase()), Buffer.from(expected));
}

Python:

import hmac, hashlib

def verify(raw_body: bytes, signature_header: str, secret: str) -> bool:
    method, _, sig = (signature_header or "").partition("=")
    if method != "sha256" or not sig:
        return False
    expected = hmac.new(secret.encode(), raw_body, hashlib.sha256).hexdigest()
    # Compare bytes: str args containing non-ASCII raise TypeError (500 instead of 401)
    return hmac.compare_digest(sig.lower().encode(), expected.encode())

In Express use express.raw({ type: '*/*' }) on the route so verification works whatever Content-Type arrives (notifications are FHIR JSON). athenahealth has no SDK helper for webhook verification, so verification is manual in every framework. The signature covers only the body: there is no timestamp or message ID, so dedupe on the event ID (see below).

For complete handlers with route wiring, event dispatch, and tests, see:

Setup

  1. Create one FHIR Subscription per topic and per practice (POST /fhir/r4/Subscription) with channel.type rest-hook and your HTTPS endpoint.
  2. Send an X-Hub-Secret: <random-shared-secret> header on that request. Set the X-Hub-Secret header when you create the FHIR subscription and paste the same value here (in Hookdeck, as the athenahealth source's webhook secret, or in ATHENAHEALTH_WEBHOOK_SECRET for the examples). Without it, notifications are unsigned.

See references/setup.md for the full steps.

Notification Shape

Each delivery is a FHIR Bundle (type: "history"). entry[0].resource is a SubscriptionStatus whose topic URL ends in the topic name (e.g. .../SubscriptionTopic/Appointment.schedule) and whose notificationEvent[] array lists one or more events, each with a unique id and a focus reference to the changed resource. One AuditEvent entry follows per event. The payload is id-only: call the athenahealth API with the focus reference to get current data.

Common Event Types

Topics are Resource.action strings:

TopicDescription
Appointment.scheduleAn appointment was scheduled
Appointment.cancelAn appointment was cancelled
Appointment.rescheduleAn appointment was rescheduled
Appointment.check-inA patient checked in for an appointment
Appointment.updateAn appointment changed
Encounter.check-inAn encounter was checked in
Encounter.signoffA clinical encounter was signed off
LabResult.createA lab result document was created
LabResult.closeA lab result was closed
Patient.create / Patient.updateA patient record was created or changed

For the full topic list, see references/overview.md and the athenahealth subscription topics

Important Headers

HeaderDescription
X-Hub-Signaturesha256=<hex> HMAC-SHA256 of the raw body (present only if you set X-Hub-Secret)

Environment Variables

ATHENAHEALTH_WEBHOOK_SECRET=your_x_hub_secret   # The X-Hub-Secret value you sent when creating the Subscription

Local Development

# Start tunnel (no account needed)
npx hookdeck-cli listen 3000 athenahealth --path /webhooks/athenahealth

Reference Materials


Repository

hookdeck/webhook-skills

v0.1.0 · MIT · Updated Nov 6, 2026

View on GitHub →