Webhooks
Who can use this feature?
- Available to all users on any plan.
- You'll need your App ID and Secret key from Settings > API. See API overview for how to generate them.
Overview
Webhooks let Chatty notify your server in real time when something happens in a conversation — for example, when your AI assistant replies, or when a customer sends a new message. Instead of polling the API, you register a URL once and Chatty POSTs a JSON payload to it whenever a matching event fires.
Common uses:
- Mirror conversations into your own helpdesk or database
- Trigger an internal alert when a customer asks something the AI couldn't answer
- Log AI replies for quality review or analytics
Events
| Event | Fires when… |
|---|---|
ai_response | The AI assistant responds to a visitor. |
customer_message | A customer sends a new message. |
A subscription can listen to one or more of these events.
Manage subscriptions
Subscriptions are managed through a small REST API. All requests go to the Chatty webhook endpoint and must include your authentication headers (see below).
| Method | Path | Purpose |
|---|---|---|
POST | /webhook/subscriptions | Create a subscription (upserts by URL). |
GET | /webhook/subscriptions | List your subscriptions. |
PUT | /webhook/subscriptions/:id | Update a subscription. |
DELETE | /webhook/subscriptions/:id | Delete a subscription. |
Authentication headers
| Header | Value |
|---|---|
X-Webhook-App-Id | Your App ID (from Settings > API). |
X-Webhook-Secret | The secret of one of your existing subscriptions. |
Create a subscription
curl -X POST https://app.chatty.net/webhook/subscriptions \
-H "X-Webhook-App-Id: YOUR_APP_ID" \
-H "X-Webhook-Secret: YOUR_SECRET" \
-H "Content-Type: application/json" \
-d '{
"url": "https://your-server.com/chatty-webhook",
"events": ["ai_response", "customer_message"],
"secret": "a-long-random-signing-secret",
"metadata": {}
}'| Field | Required | Description |
|---|---|---|
url | Yes | Where Chatty sends events. Must be https://. |
events | Yes | Array of event names to subscribe to. |
secret | Yes | Your signing secret — used to verify delivery (see below). |
metadata | No | Any object you want echoed back for your own bookkeeping. |
Creating a subscription with a url you've already registered updates that subscription instead of creating a duplicate. To pause delivery without deleting, PUT the subscription with "isActive": false.
Delivery payloads
Chatty sends a POST with a JSON body. The shape depends on the event.
customer_message
{
"timestamp": "2026-06-11T10:13:52.305Z",
"shopId": "your-shop-id",
"shopifyDomain": "your-store.myshopify.com",
"convoId": "conversation-id",
"message": {
"text": "Hi, do you have the Daris Tee in size M?",
"createdAt": "2026-06-11T10:13:52.305Z",
"customerEmail": "[email protected]",
"customerShopifyId": "gid://shopify/Customer/123"
},
"context": {
"detectedLanguage": "en",
"country": "US",
"locale": "en"
}
}ai_response
{
"timestamp": "2026-06-11T10:13:54.120Z",
"shopId": "your-shop-id",
"shopifyDomain": "your-store.myshopify.com",
"convoId": "conversation-id",
"query": "do you have the Daris Tee in size M?",
"message": {
"text": "Yes! The Daris Tee is in stock in size M.",
"topic": "product-availability",
"isAskBotResponse": "found",
"chatbotProducts": [],
"chatbotFaqs": [],
"createdAt": "2026-06-11T10:13:54.120Z"
}
}For ai_response, message.isAskBotResponse is "found" when the AI had an answer and "not-found" when it didn't — a useful signal for routing unanswered questions to a human. The chatbot* arrays (chatbotProducts, chatbotFaqs, chatbotTopics, chatbotDiscounts, chatbotPages, and others) list the sources the AI used.
Verify the signature
Every delivery includes headers so you can confirm it really came from Chatty and wasn't tampered with:
| Header | Description |
|---|---|
X-Webhook-Event | The event name (ai_response or customer_message). |
X-Webhook-Timestamp | Unix timestamp (seconds) of delivery. |
X-Webhook-Signature | HMAC-SHA256 of the raw request body, signed with your secret. |
To verify, compute the HMAC-SHA256 of the raw body using the subscription's secret and compare it to X-Webhook-Signature using a constant-time comparison.
import crypto from 'crypto'
function verify(rawBody, signatureHeader, secret) {
const expected = crypto
.createHmac('sha256', secret)
.update(rawBody)
.digest('hex')
return crypto.timingSafeEqual(
Buffer.from(expected),
Buffer.from(signatureHeader)
)
}Always verify the signature against the raw, unparsed request body. If your framework parses JSON before you can read the raw bytes, the re-serialized body may not match the signature.
Delivery behavior
- Respond fast. Chatty waits up to 10 seconds for your endpoint to respond, then aborts the request.
- No automatic retries. If your endpoint is down or slow, that delivery is dropped. Make your handler resilient — acknowledge quickly (return
2xx) and process asynchronously. - Independent delivery. Events are delivered to each subscription independently; one failing endpoint doesn't affect others.
Requirements & limits
urlmust usehttps://(for local development,http://localhostandhttp://127.0.0.1are allowed).- Each subscription has its own
secret, separate from your API Secret key.
FAQs
Can I have more than one webhook URL?
Yes. Create a separate subscription for each URL. Each one can listen to a different set of events and uses its own signing secret.
How do I temporarily stop receiving events?
PUT /webhook/subscriptions/:id with {"isActive": false}. Set it back to true to resume — you don't need to recreate the subscription.
What happens if my server is down?
The event for that window is dropped — there's no retry queue. Keep your endpoint highly available, return 2xx immediately, and do the heavy work in the background.
Need help?
If your endpoint isn't receiving events, double-check the url is publicly reachable over HTTPS and returns 2xx within 10 seconds. Still stuck? Contact the Chatty support team from your dashboard.
Chatty Help Center