Start here
Everything starts in the business console: sign in with your DOE phone number, create your business, then open Connect for keys and alert URLs and Webhooks for endpoints. Owners and admins of a business can create and revoke them.
- API keys start with
doe_live_. Keys, alert URLs and webhook secrets are shown once; DOE keeps only a hash of keys and URLs, and webhook secrets in an encrypted vault. - A business can have up to 10 keys and alert URLs together, and up to 10 webhook endpoints.
- Today an API key can send alerts. Webhooks tell you about chats, tickets and alerts.
Alerts API
Send an alert and DOE raises it in your console, pushes the people your rules name, escalates down your on-call rota and can open a ticket.
curl https://api.doe.chat/v1/alerts \
-H "Authorization: Bearer $DOE_API_KEY" \
-H "Content-Type: application/json" \
-d '{"title": "Disk 95% full", "severity": "critical", "service": "db-1", "dedupe_key": "disk-db-1"}'
Send one alert per request, as JSON.
| Field | What it is |
|---|---|
| title | Required, up to 200 characters. |
| severity | critical, warning (the default) or info. |
| service | Where it came from, up to 80 characters. |
| status | firing (the default) or resolved. |
| dedupe_key | Up to 200 characters. Alerts with the same key are one alert: repeats add to its count, resolved closes it. An alert that flips more than 4 times in 10 minutes goes quiet as flapping. |
| detail | More text for the team, up to 1,000 characters. |
| Reply | Meaning |
|---|---|
| 202 | {"received": 1}: taken. |
| 422 | The alert is wrong; message says how (no title, an unknown severity or status). |
| 403 | No key, or the key is wrong or revoked. |
| 405 / 404 | Use POST, to /v1/alerts. |
| 429 | More than 60 alerts in a minute for your business. Wait a minute. |
| 503 | DOE could not take it. Try again in a minute. |
Incoming alert URLs
For monitoring tools that post their own format, make an incoming URL in Connect and paste it into the tool’s webhook settings. No header needed; the secret is in the URL.
https://hooks.doe.chat/v1/in/<your-handle>/alerts/<token>
DOE reads Grafana, Prometheus Alertmanager, Sentry and UptimeRobot as they are, and anything else as the JSON above. The same limits and replies apply.
Webhooks
Add an endpoint in Webhooks and pick its events. DOE posts each event to it as JSON as soon as it happens.
- The URL must be
httpson a public host (notlocalhost,.localor.internal). - Reply with any 2xx within 10 seconds. Anything else is a failure.
- Failures are retried 1 minute, 10 minutes, 1 hour, 6 hours and 24 hours after the first try. After 6 tries DOE pauses the endpoint and pushes your owners and admins; while it is paused, new events are not sent to it. Turn it back on in the console.
- Delivery is at least once, and a retry can arrive after a newer event: skip an
idyou have already handled. - The console shows the last 50 deliveries with their replies and can send any of them again (same
id). Deliveries are kept 30 days.
| Header | Value |
|---|---|
| DOE-Event | The event, like message.received. |
| DOE-Delivery | This try’s id (each retry has its own). |
| DOE-Signature | t=<unix seconds>,v1=<signature> |
| User-Agent | DOE-Webhooks/1 |
{
"id": "0192b0c4-6a1e-7c3d-9f20-5b8e1d4a7c11",
"type": "message.received",
"created_at": "2026-10-21T09:14:03.512+00:00",
"data": {
"business_id": "0192a7f0-2b4c-7e11-8d3a-0c6f9e2b5a40",
"chat_id": "0192b0c3-ff02-7a6b-b1c4-3e7d0a9f6c28",
"message_id": "0192b0c4-6a1d-7f90-a2e5-8c1b4d7e3f02",
"body": "Hello, is the blue dress still in stock?"
}
}
Check signatures
Each endpoint has a signing secret (whsec_…). The signature is the hex HMAC-SHA256 of t, a full stop and the raw body, keyed with the secret. Reject anything without a valid v1 or older than 5 minutes; every try is signed afresh.
const crypto = require('node:crypto');
// header: the DOE-Signature header. body: the request body exactly as it
// arrived, before any JSON parsing (in Express: express.raw()).
function isFromDoe(header, body, secret) {
const parts = header.split(',');
const t = parts.find(p => p.startsWith('t='))?.slice(2) ?? '';
if (!(Math.abs(Date.now() / 1000 - Number(t)) < 300)) return false; // older than 5 minutes
const want = Buffer.from('v1=' + crypto.createHmac('sha256', secret).update(t + '.' + body).digest('hex'));
return parts.some(p => p.length === want.length && crypto.timingSafeEqual(Buffer.from(p), want));
}
When you roll a secret, the old one keeps working for 24 hours and the header carries two v1 values, one per secret, so you can switch without missing an event. Or end the old secret at once.
Events
Each event has id, type, created_at and data:
| Event | When | In data |
|---|---|---|
chat.started | Someone starts a chat with you, on your website or in the DOE app. | business_id, chat_id |
message.received | A customer writes to you. The message is in the event, so you need no second call. | business_id, chat_id, message_id, body |
ticket.created | A new chat opens a ticket, or an alert that keeps firing becomes one. | business_id, ticket_id (alert_id from an alert) |
ticket.updated | A ticket’s status, priority, assignee or tags change. | business_id, ticket_id, number, change |
ticket.solved | A ticket is marked solved. | business_id, ticket_id, number, change |
alert.firing | An alert starts firing. | business_id, alert_id, title, severity, source, status, count |
alert.resolved | An alert stops firing. | business_id, alert_id, title, severity, source, status, count |
order.paid | Not sent yet. It starts when orders can be paid in chat. | — |
Live chat widget
Customers chat with your team from your website; the chat lands in your console inbox with the rest.
<script src="https://doe.chat/widget.js" data-business="your-handle" async></script>
- Add
data-opento start with the chat open. - Under 30 KB, no dependencies, drawn apart from your page so your CSS can’t break it (and it can’t break yours).
- No cookies: a random key in the visitor’s browser keeps their chat going.
- Website chats are your business records, not end-to-end encrypted, and the widget tells visitors so.
- Limits against abuse: 20 messages a minute per visitor, 120 new visitors a minute per business.
Website alerts
Find out when your site breaks before customers tell you. watch.js reports your page’s JavaScript errors and failed loads as alerts.
<script src="https://doe.chat/watch.js" data-business="your-handle" async></script>
DOE ignores the reports until you turn website alerts on in Connect. It never sends page content, form values or cookies, and takes up to 30 reports a minute per business.