Webhook events
Pre-release v1, published 2026-09-14. This page commits to the shape of the API, not to a
date. We will build exactly what is documented here. The shape can still change until
2026-10-14; after that, changes follow Versioning and stability.
The banner comes off one page at a time as each route goes live. While it is here, build against the contract and assume the route is not callable yet.
Delivery
Reply 2xx as soon as the event is stored, then do your processing afterwards. A slow handler
becomes a timeout, and a timeout becomes a retry.
Verify the signature
Verify against the raw bytes, before JSON parsing. Re-serialising changes key order and
whitespace and the signature will never match. Use compare_digest, not ==.
If your endpoint needs its own authentication
Some partners cannot accept an unauthenticated POST. A bank’s gateway or WAF may require a bearer
token or an API key before the request reaches the application at all. Our signature proves the
payload came from us, but it does not satisfy your infrastructure’s rule.
You can give us a header set, and we attach it to every delivery, verbatim:
A plain bearer token works. It is just an Authorization header like any other.
The webhook URL and this token both live in your panel. Only the return URL comes to us, because it decides where a real customer’s browser is sent mid-signup.
The signature is still what proves authenticity. Your headers get the request past your own
gate; X-ParsChat-Signature tells you the body is genuinely ours. Verify the signature even
when your own auth passed. A caller who learned your token could otherwise post you fabricated
events.
Not covered: mTLS. If your infrastructure mandates client certificates, tell us before you integrate. It requires a build on our side rather than a configuration change.
A complete receiver
X-ParsChat-Delivery is stable across retries of the same event. Store it and ignore a
delivery id you have already processed. That keeps you correct when a 2xx of ours is lost in
transit and we retry an event you did handle.
Retries
Anything other than a 2xx, including a timeout, is retried with backoff: 6 attempts over about
an hour.
Then the delivery is dead-lettered and visible in the panel, so you can see what failed instead of discovering a silent gap.
Do not rely on retries as your only safety net. After the last attempt the event is not redelivered automatically. Use the usage API to reconcile a gap.
Check your configuration
Read-only, so you can verify your configuration from code without being able to change it. The secret is never returned, only whether one is set.
The envelope
Every event has the same outer shape:
schema_version is semver. Fields are added without a major bump, so ignore ones you do not
recognise. Nothing is removed or repurposed without a new major version and notice.
Event types
message.generating and is_typing are two different facts. The first is the AI composing,
the second is a human operator typing. You may want to render them differently.
Full payloads for every event: Webhook event reference.
Message types
Attachments arrive as ordinary message events with a non-text type, never as a separate event:
text · voice · image · video · multimedia · document · poll · form · template
Four fields worth explaining
external_id: your idempotency key
If you send a message with an external_id you have used before, we do not create a duplicate.
Use it to make retries safe.
reply_to_id: threaded replies
The ParsChat message_id this message replies to, so you can render the quoted message.
null when the message is not a reply.
markdown_content: rich formatting
Set when the content has formatting. text stays the plain rendering, so you can use both:
markdown in a rich chat view, plain text in a notification or SMS fallback. Either may be
null, so never assume both are present.
form_data: structured form answers
A list of {type, situation, key, value}, present when type is form.
Forms are not Instagram-only. A customer connecting through the widget produces the same shape.