Marketing CRM Webhook: API Reference (v1)
Marketing CRM Webhook: API Reference (v1)
Status: MVP (issue #852). The four events (Lead created, Case submitted, Case resolved, Payment succeeded) are tracked separately in issue #853. This reference covers the parts that are already true today: the envelope, signature verification, and deduplication.
The envelope
Every message Karpa sends (including the test ping) is wrapped in the same outer shape, called the envelope:
{ "contractVersion": 1, "eventType": "test", "eventId": "b6df...-uuid", "deliveryId": "9a31...-uuid", "occurredAt": "2026-08-01T00:00:00.000Z", "data": { "ping": true }}| Field | Meaning |
|---|---|
contractVersion |
Version of this envelope format. Bumped only if the shape changes in a way you’d need to know about. |
eventType |
What kind of thing happened (e.g. test; live event types are defined in #853). |
eventId |
A unique ID for the underlying business event (e.g. one specific Case submission). |
deliveryId |
A unique ID for this specific delivery attempt. Karpa may resend the same event if the first attempt fails. Always use this ID to avoid processing the same message twice (“deduplication,” see below). |
occurredAt |
When the real-world event happened (not necessarily when you receive it). |
data |
The actual event-specific payload; its shape depends on eventType. |
Verifying the signature (HMAC-SHA-256)
Every request includes three headers:
| Header | Meaning |
|---|---|
X-Karpa-Delivery-Id |
Same value as deliveryId in the body, convenient to read without parsing JSON. |
X-Karpa-Signature |
A hex-encoded HMAC-SHA-256 signature proving the message came from Karpa. |
X-Karpa-Timestamp |
When Karpa signed this specific delivery attempt. |
“HMAC-SHA-256” is a way to prove a message wasn’t tampered with and really came from someone who knows a shared secret (your signing secret), without ever sending that secret itself over the network.
To verify a request:
- Read the raw request body exactly as received. Do not re-format, re-indent, or re-parse-and-re-stringify it. Any change to whitespace or key order will break verification, because the signature was computed over the exact bytes Karpa sent.
- Compute
HMAC-SHA-256(your signing secret, raw body bytes), hex-encoded. - Compare that to the
X-Karpa-Signatureheader value using a constant-time string comparison (most languages have one, e.g. Node’scrypto.timingSafeEqual, Python’shmac.compare_digest). Never use a plain===/==check for this, as it can leak timing information an attacker could exploit.
Worked example (Node.js):
const crypto = require('node:crypto');
function isValidKarpaSignature(rawBody, signatureHeader, signingSecret) { const expected = crypto.createHmac('sha256', signingSecret).update(rawBody).digest('hex'); const a = Buffer.from(signatureHeader); const b = Buffer.from(expected); return a.length === b.length && crypto.timingSafeEqual(a, b);}Deduplication (why deliveryId matters)
Karpa’s delivery is at-least-once, not exactly-once, meaning if your endpoint is
slow or unreachable, Karpa will retry, and it’s possible (though rare) for the same
delivery to arrive more than once. Your receiver should keep a short-lived record of
deliveryId values it has already processed, and skip any repeat. This is much simpler
and safer than trying to guarantee “exactly once” delivery over an unreliable network.
The test event
The test delivery uses the exact same envelope and signing as a real delivery, but with
"eventType": "test" and "data": { "ping": true }. Use it to confirm your endpoint
responds with a 2xx status code and that your signature verification passes.
What’s excluded from this contract
This integration is deliberately scoped to marketing/retention use cases. It will never include: fulfillment/shipping/tracking information, or anything from the clinical chart (intake answers, clinical notes, vitals, flags, documents, messages, provider identity, diagnoses, medication dosing/directions, clinical rationale, or pharmacy details).
Event catalog
To be added in issue #853: Lead created, Case submitted, Case resolved, and Payment
succeeded, including each event’s data shape.