Webhooks
Truemed sends webhooks to notify your application when state changes in your integration. This guide covers the two authentication methods (signed and unsigned), how each delivers events, and how to verify inbound requests. See Event Types for the list of supported events and per-event payload schemas.
Configure webhook destinations and generate secrets in the Developer Dashboard.
Choose a delivery method
Truemed supports two mutually exclusive authentication methods for webhook delivery. The method is chosen per-webhook in the Developer Dashboard. Both methods use a webhook-specific secret; the difference is what we do with it at delivery time — HMAC-sign the body with it (signed) or send it verbatim in a header (unsigned).
We strongly recommend signed webhooks for any new integration. They’re tamper-evident, provide a built-in idempotency key, and cost only a few lines of code to verify. Unsigned webhooks exist for backwards compatibility; the delivery method is immutable once a webhook is created, so migrating means creating a new signed webhook alongside the unsigned one. Multiple active webhooks per sales channel are supported, so you can run both in parallel, verify the signed integration end-to-end, and retire the unsigned webhook when you’re ready — no downtime required. Manage both in the Developer Dashboard.
Signed webhooks
Envelope format
Signed webhooks deliver every event in a standard envelope:
Event types
Dot-notation event types are used in the signed envelope’s event_type field. The data shape is event-specific — follow the API Reference link for the full schema of each event payload.
Signature verification
Truemed signs the body with HMAC-SHA256 and sends the signature in the x-truemed-signature header. The header contains a timestamp and one or more versioned signatures:
The signed payload is the string {t}.{raw_body} — the timestamp from the header, a literal period, and the raw request body bytes. Because the timestamp is included in the signed message, tampering with the t value invalidates the signature.
Verification steps
- Extract the
tandv0values from thex-truemed-signatureheader. - Construct the signed payload:
{t}.{raw_body}(using the raw request body bytes, not parsed JSON). - Compute the HMAC-SHA256 hex digest of the signed payload using your signing secret.
- Compare the computed digest to
v0using constant-time comparison. - Optionally, check that
tis within your staleness tolerance (e.g. 5 minutes).
Python
Node.js
Ruby
Always compute the HMAC over the raw request body bytes exactly as received. Parsing and re-serializing JSON may change key order or whitespace, producing a different signature.
Signature versioning
The v0 prefix identifies the signing algorithm version. If Truemed ever changes the signing algorithm, a new version (e.g. v1) will be added alongside the existing one:
Your integration only needs to verify the version it was built against. Older versions will continue to be sent for backward compatibility.
Unsigned webhooks
Prefer signed webhooks for new integrations. Unsigned webhooks rely entirely on TLS and a shared-secret header — there is no payload integrity check, and the secret is sent verbatim on every request. The delivery method is immutable once a webhook is created, but multiple active webhooks per sales channel are supported — create a signed webhook alongside the existing unsigned one, run them in parallel, and retire the unsigned webhook when you’re ready. See the Developer Dashboard.
Unsigned webhooks authenticate with a plaintext API key sent in the x-truemed-api-key header. The request body is the event-specific payload delivered flat — there’s no envelope, no webhook_delivery_id, and no event_type field. Refer to the API Reference page linked for each event in Event Types for the exact body shape per event.
Authenticating requests
Unsigned webhooks have no signature to verify. Compare the received x-truemed-api-key header to your stored API key:
Payload compatibility
Over time, Truemed may add new fields to webhook payloads or introduce new event types. These additions are intended to be non-breaking. Removals, renames, field-type changes, or new values on an existing enum (for example, a new status on payment_session.completed) are treated as breaking changes and are announced in advance via the changelog.
To stay forward-compatible with additive changes, configure your JSON validator to ignore unknown fields — strict-by-default validators (e.g. Pydantic’s extra="forbid", ajv’s additionalProperties: false) will reject payloads the first time we extend one.
Responses and Retries
Return any 2xx status code to acknowledge delivery — 204 No Content is conventional. Non-2xx responses, timeouts, and connection errors are treated as failed deliveries and retried with backoff for up to 7 days.
For signed webhooks, the webhook_delivery_id is deterministic: every retry of the same event to the same webhook reuses the same ID. Use it as your idempotency key. The signature and timestamp change on each retry attempt, so do not use them for deduplication.
For unsigned webhooks, there is no built-in idempotency key. Derive one from stable fields in the payload (for example, payment_id plus status for payment_session.completed).
Best Practices
Unless noted, these apply to both delivery methods.
- Verify before processing. Reject requests with missing or invalid auth (signature for signed, API key for unsigned) before executing any business logic.
- Use constant-time HMAC comparison (signed only). When comparing the received signature to your computed digest, use a timing-safe comparator (
hmac.compare_digest,crypto.timingSafeEqual,Rack::Utils.secure_compare) rather than==, so the comparison can’t leak the signature byte-by-byte. - Deduplicate deliveries. Signed webhooks give you a
webhook_delivery_idfor free; for unsigned webhooks, derive an idempotency key from stable payload fields. - Check staleness (signed only). Compare the header timestamp
tto the current time and reject events outside your tolerance window (e.g. 5 minutes). Unsigned deliveries don’t carry a timestamp. - Keep your secret safe. Store the signing secret or API key in environment variables or a secrets manager — never hard-code it in source control.
- Rotate secrets periodically. Use the Developer Dashboard to rotate your signing secret or API key without downtime.