Webhook Patient Leads Safely: A Field Guide
How to move patient leads over webhooks safely: authentication, HMAC signature verification, payload design, retries, idempotency, and what never belongs in a body.
A patient lead webhook is safe when it is authenticated with a verified signature rather than an unguessable URL, carries only the fields the receiver actually needs, never puts identifying or clinical values in the URL, and is idempotent so retries cannot create duplicate records or duplicate conversions. Curve is the HIPAA-compliant tracking layer that receives these events server-side, matches them to attribution by email, click ID, or bridge token, and forwards only mapped, hashed fields to ad platforms. A signed BAA is included on every plan.
Webhooks are how a lead becomes a measurable outcome. The form vendor tells your backend a submission happened. The CRM tells your measurement layer that the lead booked. The call tracking platform tells both that someone rang. Each hop is a small HTTP request, and each one is a place where healthcare data quietly leaves a system that has a Business Associate Agreement behind it and enters one that does not.
The two questions to ask about every webhook
Before any implementation detail, settle these:
- Does the receiving system have a signed BAA? If yes, the webhook may carry protected health information, subject to the usual safeguards. If no, it may carry only non-clinical identifiers and neutral event signals, and even those deserve scrutiny.
- What does the receiver genuinely need to do its job? Not what the sender happens to have. Most webhook payloads are bloated because someone serialized an entire object and moved on.
Those two answers determine the payload. Everything below is about transporting it without introducing a second problem.
Authentication: a secret URL is not authentication
The most common webhook design in marketing stacks is a long random URL treated as a password. It is not one. URLs appear in browser history, referrer headers, proxy logs, load balancer access logs, CDN logs, error reports, and screenshots pasted into support tickets. A URL is an identifier, not a credential.
Three real options, in ascending order of strength.
Shared secret in a header
The sender includes a static secret in a header such as X-Webhook-Key, and the receiver compares it against a stored value using a constant-time comparison. This is the minimum acceptable bar. It is simple, it works with any sender, and it fails safely if the secret is rotated. Its weakness is that the secret is replayed on every request, so any party that can read one request can impersonate the sender indefinitely.
HMAC signature over the raw body
The sender computes an HMAC (usually SHA-256) over the exact request body plus a timestamp, using a shared signing key, and sends the result in a header. The receiver recomputes it and compares. The secret itself never crosses the wire, and the signature proves the body was not modified in transit. This is the right default for anything carrying patient data.
Mutual TLS
Both sides present certificates. Strong, and appropriate between systems you control. Most SaaS senders do not support it, so in practice HMAC is where you land.
IP allowlisting is a useful additional layer and a poor primary control. Vendor egress ranges change without notice, and an allowlist tells you where a request came from, not who sent it or whether it was modified.
Verifying a signature without getting it subtly wrong
Signature verification is easy to implement and easy to implement in a way that does not actually verify anything. Five failure modes account for most of it.
- Verifying the parsed body instead of the raw bytes. Most frameworks parse JSON before your handler runs. Re-serializing changes key order, whitespace, and unicode escaping, so the signature will never match, and the usual fix applied under deadline pressure is to stop verifying. Capture the raw body before parsing.
- Comparing with
==. String equality short-circuits on the first differing byte, which leaks timing information. Use a constant-time comparison function. - Ignoring the timestamp. A valid signature is valid forever unless you bind it to time. Require a timestamp header, include it in the signed payload, and reject anything outside a tolerance window of a few minutes.
- Failing open. Missing header, malformed signature, unknown key version: all of these must return a rejection, not a success. Log the rejection with the reason. Never let an exception in the verification path fall through into processing.
- No key rotation path. Support two active signing keys with a version identifier so you can rotate without downtime. A secret that cannot be rotated will not be rotated, and it will eventually appear in a screenshot.
Payload design: reference, do not carry
The single most useful habit is to send the smallest thing that lets the receiver act, and let it fetch the rest over an authenticated channel if it genuinely needs more. A thin webhook is easier to secure, easier to log, and easier to reason about when someone asks what left the building.
Reasonable in a webhook body to a BAA-covered receiver
- A stable record identifier the receiver can use to fetch details over an authenticated API
- An event type and a timestamp
- Contact identifiers required for matching, such as email or a normalized phone number
- The click identifier captured at landing, or a bridge token
- Non-clinical operational metadata such as location code or lead source, where those are not condition-specific
Never in a webhook body crossing into a system without a BAA
- Reason for visit, symptoms, diagnosis codes, or any clinical description
- Medications, dosages, or treatment history
- Insurance carrier, member number, or coverage details
- Medical record numbers or any internal patient identifier
- Free-text message, note, or comment fields of any kind
- Appointment type, service line, department, or provider name where those map to a specialty
- Call recordings, transcripts, or links to either
- Date of birth or Social Security number
- Full page URLs and referrers that name a condition or treatment
Never in a URL, to any receiver, ever
This one is absolute and it is the mistake most often found during an audit. Query strings and path segments are logged by every intermediary in the chain: the sender's HTTP client, your load balancer, your web server, your CDN, your error tracker, your APM tool. Once an email address or an appointment type is in a query string, it is in a dozen log stores you did not choose, most of which have their own retention policies and their own access lists.
Identifiers and event data belong in the request body or in headers. If a vendor's integration only supports templating values into a URL, that is a reason to reject the integration, not a reason to make an exception.
The free-text problem deserves its own note. A message box is a blank cheque written by the patient, and patients write remarkably specific things in them. There is no reliable way to sanitize free text at scale, so exclude it from any payload leaving a BAA-covered boundary rather than attempting to clean it. Route it once, to the system where the intake team reads it, and stop.
Retries, idempotency, and duplicate conversions
Every serious webhook sender retries. Networks fail, deploys restart processes, and a 500 you returned at 3am will come back. Retries are correct behavior, and they mean the same event will arrive more than once. Design for that or you will report conversions you did not earn.
Give every event a stable identifier. The sender should include an event ID that stays constant across retries of the same event. If a sender does not provide one, derive a deterministic key from stable fields (record ID plus event type plus event timestamp) rather than from the arrival time.
Deduplicate on receipt. Store processed event IDs with a time-to-live comfortably longer than the sender's retry window. Check before processing, not after. A unique constraint in the database is a better guarantee than an application-level check, because two workers can race.
Acknowledge fast, process asynchronously. Verify the signature, write the event to a durable queue, return a 2xx. Do not do CRM lookups, ad platform calls, or anything else slow inside the request. Senders time out in seconds, and a timeout produces a retry, which produces the duplicate you were trying to avoid.
Use status codes deliberately. Return 2xx when you have durably accepted the event. Return 4xx for something a retry will never fix, such as a bad signature or a malformed body, so the sender stops. Return 5xx only for genuinely transient failures where a retry might succeed. Returning 200 to make a red dashboard go green discards events permanently.
Carry idempotency through to the destination. Deduplicating at your edge is not enough if the downstream ad platform can still receive the same conversion twice. Pass a stable event identifier to the platform's conversion API so its own deduplication can work. The technique is the same one used for browser and server event deduplication, covered in our technical overview of conversion API architecture.
Failure handling, logging, and the things audits find
Webhook endpoints fail quietly, and the failure mode that matters is data that stopped arriving three weeks ago and nobody noticed. Alert on volume anomalies rather than on individual errors: a webhook that normally receives a few hundred events a day and received none since Tuesday is the signal worth waking up for.
Failed events need somewhere to go. A dead-letter store with the raw payload lets you replay after fixing a bug, which is far better than losing a week of conversions. That store now holds patient data, so it belongs inside the same BAA and encryption boundary as everything else, with a retention rule, not in a developer's S3 bucket.
On logging, two habits prevent most audit findings. Log metadata rather than bodies: event ID, event type, timestamp, signature verification result, processing outcome. And check what your error tracker captures automatically, because most attach request bodies and headers to exceptions by default. That default has put more PHI into third-party systems than any deliberate decision.
How Curve handles incoming webhooks
Curve is HIPAA-compliant ad tracking, attribution, and analytics for healthcare, and incoming webhooks are how downstream outcomes rejoin the measurement layer.
- Authenticated server-side receipt. Events land on Curve's US-hosted infrastructure, not on an ad platform. That is the structural point: there is somewhere to make a decision before anything leaves.
- Attribution matching. Incoming events are matched to the original session by email, click ID, or bridge token, so a booking recorded in your CRM days later reconnects to the ad click that produced it.
- Protected core fields. Incoming webhook data cannot override protected core attribution and contact fields. A misconfigured upstream system can fail to add information; it cannot corrupt the record you already have.
- Per-destination field mapping. Only explicitly mapped fields forward to a given ad platform, and the default is that nothing goes. A field that appears in a webhook body next month does not silently start travelling to Meta or Google.
- Identifier hashing. Contact identifiers are SHA-256 hashed per each platform's conversion API requirements before forwarding.
- Neutral event aliases. The ad platform records a neutral event name, so the service line never appears in its interface even when your internal event names are descriptive.
- PHI-pattern detection. Payloads carrying PHI-shaped values such as SSNs, MRN-style identifiers, dates, and long numeric sequences are flagged. This is monitoring, not redaction. It tells you when an upstream form or CRM automation changed, which is how these leaks usually begin.
- Offline conversion uploads. Where a webhook is not available, bulk CRM or EHR exports upload with click ID matching, up to 10,000 rows or 5MB per file.
A signed BAA is included on every plan.
Frequently asked questions
Can a webhook carry PHI at all?
Yes, between systems where the receiving vendor has signed a BAA and appropriate safeguards are in place. HIPAA does not forbid transmitting PHI; it governs who may receive it and how it must be protected. The prohibition is on sending it to a vendor with no BAA, which is why ad platform destinations get a stripped-down payload.
Is HTTPS enough on its own?
No. TLS protects data in transit against interception. It says nothing about who sent the request or whether the body was modified before it was signed and sent. You need transport encryption and authentication, not one instead of the other.
What timestamp tolerance should I use for replay protection?
A few minutes is the usual range. Too tight and legitimate requests fail when clocks drift or a sender queues behind a backlog. Too loose and a captured request stays replayable for longer than it should. Whatever you pick, make sure the timestamp is part of the signed payload, or an attacker simply edits it.
Should I verify the signature before or after parsing the body?
Before, and over the raw bytes. Parsing then re-serializing changes the exact byte sequence that was signed, which is the most common reason a correct implementation appears broken.
What happens if my endpoint is down for an hour?
It depends entirely on the sender's retry policy, which is why you should read it before you need it. Most retry with exponential backoff for a fixed window, then give up. Anything dropped after that window is gone unless the sender exposes a backfill API. Alerting on missing volume, plus a dead-letter store, is how you avoid discovering this a month later.
How do I check whether an existing integration is leaking?
Capture a real payload in a controlled environment and read every field, including the ones nobody documented. Then check what your logs, error tracker, and any middleware run history retained from that same request. Our free compliance scanner covers the site side, and our guide to lead routing from ad click to CRM covers the flow around it.
Where to start
Pick your busiest inbound webhook and answer three questions about it today. Is the request authenticated by a verified signature rather than a secret URL? Does the body contain anything the receiver does not need, especially free text? Would a retry create a second record or a second conversion?
Most stacks fail at least one of those, and the fixes are small. Then look at the boundary: which of these hops crosses into a vendor without a BAA, because that is where a payload design problem becomes a disclosure. Curve sits on that boundary by design, with authenticated server-side receipt, attribution matching by email, click ID, or bridge token, per-destination field mapping, hashed identifiers, neutral event aliases, and PHI-pattern monitoring, plus a signed BAA on every plan. See how the whole path fits together in connecting lead forms to your CRM without PHI, or visit curvecompliance.com.
Reviewed August 2026. Webhook signing schemes, retry policies, and conversion API deduplication rules vary by vendor and change frequently. Verify current specifications against each vendor's documentation before implementation.
Stay Compliant. Scale Confidently.
Join healthcare innovators who trust Curve for HIPAA-compliant ad tracking.Launch in hours, not months. Your growth stack, now HIPAA-safe.
Book a free tracking audit