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-Signatureheader from athenahealth? - Why is my athenahealth webhook signature verification failing?
- How do I handle
Appointment.schedule,Appointment.cancel,Encounter.signoff, orLabResult.createevents? - 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
- Create one FHIR
Subscriptionper topic and per practice (POST /fhir/r4/Subscription) withchannel.typerest-hookand your HTTPS endpoint. - Send an
X-Hub-Secret: <random-shared-secret>header on that request. Set theX-Hub-Secretheader when you create the FHIR subscription and paste the same value here (in Hookdeck, as the athenahealth source's webhook secret, or inATHENAHEALTH_for the examples). Without it, notifications are unsigned.WEBHOOK_ SECRET
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:
| Topic | Description |
|---|---|
Appointment.schedule | An appointment was scheduled |
Appointment.cancel | An appointment was cancelled |
Appointment.reschedule | An appointment was rescheduled |
Appointment.check-in | A patient checked in for an appointment |
Appointment.update | An appointment changed |
Encounter.check-in | An encounter was checked in |
Encounter.signoff | A clinical encounter was signed off |
LabResult.create | A lab result document was created |
LabResult.close | A lab result was closed |
Patient.create / Patient.update | A patient record was created or changed |
For the full topic list, see references/overview.md and the athenahealth subscription topics
Important Headers
| Header | Description |
|---|---|
X-Hub-Signature | sha256=<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
- references/overview.md - Event Subscription Platform concepts, topics, Bundle payload, delivery semantics
- references/setup.md - Create a Subscription with a secret and point it at your endpoint
- references/verification.md - Signature verification details and gotchas