Live Chat Webhooks
Webhooks let onWebChat push events to your own server in real time. Whenever something happens (a new chat starts, a message is sent, a chat is closed or rated, a visitor leaves an offline message), we send an HTTP POST with a JSON body to a URL you choose.
This is the easiest way to sync chat transcripts into your CRM or CMS, trigger follow-up emails, build custom reports, or notify another system, with no polling required.
Webhooks are available on all paid plans and during the 14-day free trial. Each delivery is signed so you can verify it really came from onWebChat.
POST https://your-server.com/onwebchat-webhook
Content-Type: application/json
User-Agent: onWebChat-Webhooks/1.0
X-OWC-Event: chat.closed
X-OWC-Delivery: 9381
X-OWC-Signature: sha256=4f1c...c2a9
{ "event": "chat.closed", "siteid": 1234, ... }
Setting Up Webhooks
Log in to your dashboard as an admin and go to Settings → Webhooks. Click "Add endpoint", paste the URL that should receive events, choose which events you want (or select "All events", which also covers events we add later), and save.
When you create an endpoint, we generate a signing secret for it (it starts with whsec_). You will use this secret to verify incoming requests (see Verifying Signatures below). You can regenerate the secret at any time; the old one stops working immediately.
You can add multiple endpoints per website and review recent deliveries, including the HTTP status, number of attempts and any error, directly in the dashboard.
/* Dashboard → Settings → Webhooks */
URL : https://your-server.com/onwebchat-webhook
Events : chat.closed, message.created
Secret : whsec_3f9a... (used to verify requests)
Status : Enabled
Event Types
Subscribe to any combination of the events below. Every message carries an "author": visitor, agent or ai (plus trigger, for trigger messages in the chat.closed transcript), so the same message.created event covers conversations handled by your team and by the AI chatbot.
| Event | Sent when |
|---|---|
chat.created | A new chat (dialog) starts. The source field says how: visitor, trigger or agent. |
chat.assigned | An agent takes ownership of a chat: picks it up, takes it over from another agent or the AI chatbot, or accepts a transfer. The source field is pickup, reassign, ai_takeover or transfer. |
message.created | A new message is sent in a chat. Includes messages from the visitor, an agent, and the AI chatbot. |
chat.closed | A chat ends. The payload includes the full transcript, visitor details and labels. |
chat.rated | A visitor rates a chat (1 to 5 stars). |
offline_message.created | A visitor submits the offline / contact form while no agent is available. |
visitor.updated | An agent updates the visitor's name, email, phone or notes from the chat dashboard. |
visitor.banned | An agent blocks a visitor from the chat dashboard. |
visitor.unbanned | An agent unblocks a previously blocked visitor from the chat dashboard. |
Payload Format
Every webhook is a JSON object with the same envelope: the event name, your siteid, an ISO-8601 timestamp, and a data object whose shape depends on the event.
The example shows a chat.closed payload, which carries the whole conversation. Smaller events such as message.created or chat.rated include just the relevant fields.
{
"event": "chat.closed",
"siteid": 1234,
"created_at": "2026-06-22T07:40:52.322Z",
"data": {
"chat_id": 1206836,
"started_at": "22-06-2026 10:40",
"closed_at": "2026-06-22T07:40:52.322Z",
"visitor": {
"name": "Sarah Chen",
"email": "sarah@example.com",
"phone": "",
"notes": "",
"country_code": "us",
"country": "United States",
"os": "Windows 10",
"browser": "Chrome 149.0"
},
"agents": ["Alex"],
"labels": [{ "name": "sales", "color": "#4caf50" }],
"messages": [
{ "author": "visitor", "agent_name": "", "text": "pricing info please", "time": "10:40", "date": "22-06-2026" },
{ "author": "ai", "agent_name": "", "text": "Here is a quick overview...", "time": "10:40", "date": "22-06-2026" },
{ "author": "agent", "agent_name": "Alex", "text": "Hi Sarah, I can help with a quote.", "time": "10:41", "date": "22-06-2026" }
]
}
}
Verifying Signatures
Every request includes an X-OWC-Signature header containing an HMAC-SHA256 hash of the exact raw request body, computed with your endpoint's signing secret.
To verify a request, compute the HMAC-SHA256 of the raw body using your secret and compare it to the header value (the part after "sha256="). Use a constant-time comparison and always hash the raw body, not a re-serialized version.
You also receive X-OWC-Event (the event name) and X-OWC-Delivery (a unique delivery ID that stays the same on retries, so you can use it for de-duplication). Requests are sent with the User-Agent onWebChat-Webhooks/1.0.
// Node.js / Express
const crypto = require('crypto');
app.post('/onwebchat-webhook',
express.raw({ type: '*/*' }), (req, res) => {
const secret = 'whsec_your_secret';
const expected = 'sha256=' + crypto
.createHmac('sha256', secret)
.update(req.body) // raw Buffer
.digest('hex');
const got = req.headers['x-owc-signature'] || '';
// constant-time compare; guard length first (timingSafeEqual throws on mismatch)
const a = Buffer.from(got), b = Buffer.from(expected);
if (a.length !== b.length || !crypto.timingSafeEqual(a, b)) return res.status(401).end();
const payload = JSON.parse(req.body.toString());
// ... handle payload.event ...
res.sendStatus(200);
});
Verifying in PHP
The same check in PHP: read the raw request body, recompute the HMAC with your secret, and compare with hash_equals().
<?php
$secret = 'whsec_your_secret';
$payload = file_get_contents('php://input');
$got = $_SERVER['HTTP_X_OWC_SIGNATURE'] ?? '';
$expected = 'sha256=' . hash_hmac('sha256', $payload, $secret);
if (!hash_equals($expected, $got)) {
http_response_code(401);
exit;
}
$event = json_decode($payload, true);
// ... handle $event['event'] ...
http_response_code(200);
Retries & Auto-Disable
Your endpoint should respond with a 2xx status as quickly as possible, and within 10 seconds at most. Any non-2xx response, a timeout, or a connection error is treated as a failed attempt.
Failed deliveries are retried with an increasing delay: after 1 minute, 5 minutes, 20 minutes, 1 hour and 2 hours (6 attempts in total).
If 5 deliveries in a row still fail after all their retries, the endpoint is disabled automatically and the account owner gets an email. Re-enable it from the dashboard once the issue is fixed. Returning HTTP 410 (Gone) disables the endpoint immediately.
Attempt 1 → immediately
Attempt 2 → after 1 minute
Attempt 3 → after 5 minutes
Attempt 4 → after 20 minutes
Attempt 5 → after 1 hour
Attempt 6 → after 2 hours
5 failed deliveries in a row → endpoint disabled
Best Practices
- Always verify the X-OWC-Signature before trusting a payload.
- Respond 2xx fast, then do heavy work asynchronously (queue it) so you do not time out.
- Use X-OWC-Delivery to de-duplicate, because the same delivery may be retried after a transient failure.
- Serve your endpoint over HTTPS.
- Ignore unknown event types so new events we add later do not break your integration.
Ready to start? See plans or open Settings → Webhooks in your dashboard.