Skip to main content
On a HIPAA-compliant workspace, conversations are automatically deleted once they go idle for more than 24 hours or reach 7 days old. The HIPAA conversation webhook lets you keep that data: Chatbase sends each conversation to an endpoint you control, as JSON, just before it is deleted. You configure one webhook per workspace, covering every AI agent in it.
The webhook delivery is the only copy of the conversation you will get. After deletion, Chatbase retains only the redacted version, so the unredacted content of a conversation that was never delivered cannot be recovered or resent. Verify your endpoint before conversations start flowing, and keep your signing secret in sync.
For the retention rules themselves, see Conversation auto-end rules.

Requirements

  • A HIPAA-compliant workspace — Enterprise plan with a signed BAA. See HIPAA compliance.
  • An HTTPS endpoint that accepts POST requests. Plain http:// URLs are rejected.
  • Permission to edit workspace settings. Workspace Owners have this by default; it can be granted to other members through a custom role.

Configure your endpoint

1

Open the HIPAA settings

Go to Settings → HIPAA in your dashboard and find the Webhook configuration card.The HIPAA settings page only appears on workspaces where HIPAA compliance is enabled.
2

Save your endpoint URL

Enter your endpoint in the Webhook endpoint field and click Save.
Enter the final URL — redirects are not followed. PHI must only ever reach the endpoint you verified, and a redirect to http:// would put it on the wire in cleartext.Redirects are almost always accidental. The usual causes:
  • a trailing slash your framework normalises (Next.js, Django’s APPEND_SLASH, Rails)
  • example.comwww.example.com canonicalisation
  • a path that moved, leaving a 301 behind
  • platform-level rules — Vercel or Netlify redirects, Cloudflare Page Rules
Verification fails if either probe is redirected, and a delivery that is redirected is retried and eventually abandoned, the same as any other failure.
3

Copy the signing secret

Saving a URL for the first time generates a signing secret — a 64-character hex string — and shows it in a one-time modal.
The secret is shown once. Copy it into your secrets manager before closing the modal. Afterwards the dashboard displays only the last four characters, and the only way to get a usable value again is to regenerate it.
4

Verify the endpoint

Click Verify endpoint. Your endpoint must pass this check before any conversation is delivered — see Endpoint verification below.
Verify endpoint stays disabled until there is a saved URL and a signing secret, and while the URL field has unsaved edits. Save your changes first, then verify.

Endpoint verification

Verification proves your endpoint is safe to receive PHI. Clicking Verify endpoint sends two POST requests in sequence to the same URL: Both must hold for verification to pass. The second probe is the point of the design. An endpoint that returns 200 to anything would look perfectly healthy while accepting forged patient data from anyone who guessed its URL. Requiring a rejection proves your receiver actually checks the signature — and it only asks for behaviour you need in production anyway, so there is no verification-only code to write and later remove.
Both probes carry "test": true, which real deliveries never do. Acknowledge them and do not store them.They also deliberately use different delivery_id values, so a correctly idempotent receiver does not discard the second one as a duplicate.
If probe 1 fails, probe 2 is never sent — so an endpoint that rejects everything shows only a single request in its own logs.

Verification failure reasons

Until verification passes, the card shows Endpoint not verified and nothing is delivered. Conversations that reach their retention threshold in the meantime are held and retried — but they spend retry attempts while they wait, so verify early.

The delivery request

Each conversation is sent as its own POST request with a JSON body.

Headers

Payload

Envelope fields

Conversation fields

The conversation object uses snake_case keys.
Route on conversation.chatbot_id if several of your agents share the same webhook — there is no separate agent object in the envelope.

Source values

source records the channel the conversation arrived through. On a HIPAA-compliant workspace you can expect: Treat the list as open-ended: new channels add new values, so route with a fallback rather than an exhaustive match.

Message shape

messages holds the conversation history, oldest first.
Message keys are camelCase (createdAt, toolCallId, originalFileName) even though the enclosing conversation object is snake_case.

Message fields

These can appear on a message of any role. Every field except role is optional, so check for presence rather than assuming a fixed shape. Assistant messages may also carry: User messages may also carry:
source means two different things at two different levels. conversation.source is the channel (Widget or Iframe, API, …); a message’s source is where that answer came from (llm, qna, …). They share a name but not a value set.

Content parts

content is a string on plain text messages and an array on tool-call and tool-result messages. Calling something like String(content) breaks on any conversation where the agent used an action. input and output are action-specific and, for custom actions and forms, contain whatever your own integration returned. Treat them as opaque JSON unless you know the action.

Attachments

Parsing notes

  • createdAt is historically inconsistent. Conversations backfilled through the API may use a space instead of T, or omit the minutes in the UTC offset. Parse it defensively.
  • Ignore fields you do not recognise rather than treating them as an error, so a future addition does not break your receiver.

Example

Verify the signature

Every request carries an HMAC-SHA256 signature. Recompute it and reject anything that does not match — otherwise anyone who learns your endpoint URL can post fabricated patient data to it. The signature is computed over the timestamp, a literal ., and the raw request body:
The v1= prefix identifies the scheme, so it can change in future without breaking existing receivers. Compare the full v1=… string, prefix included.
Verify against the raw request body. This is by far the most common integration failure. The signature covers the exact bytes on the wire, so if your framework parses the JSON and you re-serialize it to hash, key order or whitespace shifts and the HMAC never matches.
  • Expressexpress.raw({ type: 'application/json' }), not express.json()
  • Next.js (App Router)await request.text(), not request.json()
  • Next.js (Pages Router)export const config = { api: { bodyParser: false } } plus raw-body
  • Flaskrequest.get_data(), not request.get_json()
Your receiver should, in this order:
  1. Reject missing headers with a 4xx, rather than letting them fall through and look like a signature mismatch.
  2. Check the timestamp is recent — within about 5 minutes. The timestamp is part of the signed input, but only checking it makes replay protection real; without this check, a captured request stays valid forever.
  3. Take the timestamp from the header, not from your own clock. A locally generated one will never match.
  4. Recompute the HMAC and compare in constant time.
  5. Only then parse and trust the body. Never act on payload contents before the signature checks out.
Each example above accepts a correctly signed request and rejects a forged one, so it passes verification as written.
A plain === (or ==) on strings stops at the first byte that differs, so how long the comparison takes leaks how many leading bytes matched. In principle an attacker can send many requests, measure the response times, and recover a valid signature one byte at a time — turning an infeasible search into roughly a thousand guesses.In practice, network jitter dwarfs the timing difference, so this is hardening rather than a likely attack path. But it costs nothing: use crypto.timingSafeEqual in Node, hmac.compare_digest in Python, or your language’s equivalent.If your platform has no constant-time primitive, hash both values again with a random per-request key and compare those results normally. An attacker cannot steer timing against a key they do not know.

Responding

  • Return any 2xx status to acknowledge the delivery. Any other status is treated as a failure and retried.
  • Chatbase closes the connection after 10 seconds. Acknowledge first and process asynchronously — slow processing turns into timeouts, which turn into duplicate deliveries.
  • Your response body is ignored.

Retries

If a delivery fails, Chatbase retries it on a widening schedule: That is 6 attempts over roughly 22 hours. Each wait is jittered by ±20%, so a batch of deliveries that fails together — one outage, one bad deploy — does not come back as a synchronised burst. A failure is any non-2xx response, a timeout, or a connection, DNS, or TLS error.
After the sixth failed attempt the delivery is abandoned, and because the conversation has already been deleted, its content is gone. There is currently no manual retry.An endpoint that is down for an evening will recover on its own. One that is down for a full day will lose data.
Attempts are also consumed while the webhook itself is not ready to receive: a delivery waiting on an unverified endpoint, or on a missing signing secret, uses up an attempt each time it is tried. Verifying your endpoint before conversations start reaching their retention thresholds is what keeps the ladder available for real failures.

Idempotency

Delivery is at-least-once, so your receiver must tolerate duplicates. Retries reuse the same X-Chatbase-Delivery-Id. A delivery can also arrive twice legitimately — if your endpoint processed a request but the acknowledgement was lost on the way back, Chatbase never recorded the success and will send it again. Deduplicate on delivery_id, or on conversation.id, which appears in at most one conversation’s worth of deliveries. Record the ID in the same transaction that stores the conversation so a crash between the two cannot lose or double-count it.

Changing your endpoint or secret

Two rules explain the whole table:
  • The current secret is used at the moment of each attempt. Rotating is a security action — if you rotate because you believe the old secret leaked, the very next delivery must not still be signed with it. So rotation takes effect immediately, including for conversations already waiting.
  • Verification attests to one specific endpoint. A different URL has not proved anything, so changing it clears verification and you must verify again.
To rotate without downtime, accept both the old and the new secret for a short window: deploy a receiver that tries the current secret and falls back to the previous one, regenerate in the dashboard, then remove the old value once traffic confirms the new one is in use.
Remove webhook deletes the endpoint URL and the signing secret. Conversations stop being delivered anywhere, and anything still awaiting delivery will not arrive. You can configure a new webhook later, but it is issued a fresh secret — the old one cannot be recovered.

Troubleshooting

Your endpoint returned 2xx to a request with a deliberately invalid signature. Either it is not checking the signature at all, or it returns a response before the check runs — a common shape is an early return res.status(200) for health checks or an OPTIONS/POST handler that acknowledges first and validates later.Check that an invalid signature produces a non-2xx status, and that the check happens before anything else responds.
Your endpoint returned a non-2xx to a correctly signed request. The usual cause is raw-body handling — see the warning in Verify the signature.Also confirm the endpoint is publicly reachable over HTTPS and is not behind authentication, an IP allowlist, or a WAF rule that blocks unknown callers.
Your endpoint answered with a 3xx instead of handling the request. Redirects are not followed, so the URL you save has to be the one that actually serves the webhook.Most often this is a trailing slash or www canonicalisation rather than anything you configured deliberately — see the full list of causes under Configure your endpoint. Sending a POST to your saved URL with curl -i and checking for a Location header is the quickest way to confirm it.
In order of likelihood:
  1. The body was re-serialized. A JSON body parser ran before you computed the HMAC. Use the raw bytes.
  2. The timestamp came from the wrong place. It must be read from the X-Chatbase-Timestamp header, not generated locally.
  3. The signed input is malformed. It is timestamp + "." + rawBody — a literal period between the two, and nothing else.
  4. The secret has stray whitespace. A trailing newline picked up when pasting into an environment file or secrets manager will change every digest.
  5. The v1= prefix was dropped. Compare the whole header value, including v1=.
The first probe failed, so the second was never sent. Fix the failure reason shown in the dashboard and verify again.
Expected behaviour — delivery is at-least-once. Deduplicate on delivery_id. See Idempotency.
That is a verification probe, not a real conversation. Acknowledge it with a 2xx and do not store it, or you will save a fake conversation into your records.
Check, in order:
  1. The Webhook configuration card shows Endpoint verified. If it shows Endpoint not verified, nothing is being delivered.
  2. Conversations have actually reached a retention threshold — a conversation is only delivered when it is deleted, so nothing arrives for conversations that are still active or idle for less than 24 hours.
  3. Your endpoint URL is still correct, and saving it did not silently reset verification.

Notes and limits

  • One webhook per workspace, covering every AI agent in it. Use conversation.chatbot_id to tell them apart.
  • One conversation per request. Deliveries are never batched, so each conversation gets its own status code and can succeed or fail independently.
  • HTTPS only.
  • Deletion is never delayed. Conversations are deleted on schedule whether or not delivery succeeds, so your endpoint being down does not extend the retention window.
  • Chatbase does not log the conversation content of a delivery. Delivery attempts are recorded for support and audit purposes with only the outcome, response status, and error reason. Your endpoint’s hostname is recorded in audit events; the full URL and the signing secret never are.

HIPAA compliance overview

Retention rules, redaction, disabled features, and the shared responsibility model for HIPAA-compliant workspaces.