PHP
$body = file_get_contents('php://input');
$sig = [];
foreach (explode(',', $_SERVER['HTTP_ONEANSWER_SIGNATURE'] ?? '') as $part) {
[$k, $v] = array_pad(explode('=', $part, 2), 2, '');
$sig[$k] = $v;
}
$expected = hash_hmac('sha256', ($sig['t'] ?? '').'.'.$body, getenv('ONEANSWER_WEBHOOK_SECRET'));
if (! hash_equals($expected, $sig['v1'] ?? '') || abs(time() - (int) ($sig['t'] ?? 0)) > 300) {
http_response_code(400);
exit;
}
$event = json_decode($body, true);
Node.js (Express)
const crypto = require('crypto');
app.post('/oneanswer', express.raw({ type: 'application/json' }), (req, res) => {
const sig = Object.fromEntries((req.get('Oneanswer-Signature') || '').split(',').map(p => p.split('=')));
const expected = crypto.createHmac('sha256', process.env.ONEANSWER_WEBHOOK_SECRET)
.update(`${sig.t}.${req.body}`).digest('hex');
const given = Buffer.from(sig.v1 || '');
const fresh = Math.abs(Date.now() / 1000 - Number(sig.t)) < 300;
if (!fresh || given.length !== expected.length || !crypto.timingSafeEqual(given, Buffer.from(expected))) {
return res.sendStatus(400);
}
const event = JSON.parse(req.body);
res.sendStatus(200);
});
Python
import hashlib, hmac, os, time
def verify(raw_body: bytes, header: str) -> bool:
sig = dict(p.split("=", 1) for p in header.split(",") if "=" in p)
expected = hmac.new(os.environ["ONEANSWER_WEBHOOK_SECRET"].encode(),
f"{sig.get('t', '')}.".encode() + raw_body, hashlib.sha256).hexdigest()
fresh = abs(time.time() - int(sig.get("t", 0))) < 300
return fresh and hmac.compare_digest(expected, sig.get("v1", ""))
appointment.booked · Appointment booked
{
"data": {
"reference": "K7P4M",
"status": "booked",
"service": "Men’s haircut",
"calendar": "Sam",
"starts_at": "2026-10-22T14:00:00+01:00",
"ends_at": "2026-10-22T14:30:00+01:00",
"source": "call",
"customer": {
"name": "Sam Taylor",
"phone": "+447700900123",
"email": null
}
}
}
appointment.rescheduled · Appointment moved
{
"data": {
"reference": "K7P4M",
"status": "booked",
"service": "Men’s haircut",
"calendar": "Sam",
"starts_at": "2026-10-22T14:00:00+01:00",
"ends_at": "2026-10-22T14:30:00+01:00",
"source": "call",
"customer": {
"name": "Sam Taylor",
"phone": "+447700900123",
"email": null
}
}
}
appointment.cancelled · Appointment cancelled
{
"data": {
"reference": "K7P4M",
"status": "booked",
"service": "Men’s haircut",
"calendar": "Sam",
"starts_at": "2026-10-22T14:00:00+01:00",
"ends_at": "2026-10-22T14:30:00+01:00",
"source": "call",
"customer": {
"name": "Sam Taylor",
"phone": "+447700900123",
"email": null
}
}
}
appointment.completed · Appointment completed
{
"data": {
"reference": "K7P4M",
"status": "booked",
"service": "Men’s haircut",
"calendar": "Sam",
"starts_at": "2026-10-22T14:00:00+01:00",
"ends_at": "2026-10-22T14:30:00+01:00",
"source": "call",
"customer": {
"name": "Sam Taylor",
"phone": "+447700900123",
"email": null
}
}
}
lead.created · New lead
{
"data": {
"id": "01JC…",
"status": "qualified",
"score": 80,
"interest": "Kitchen refit quote",
"contact": {
"name": "Sam Taylor",
"phone": "+447700900123"
}
}
}
lead.updated · Lead status changed
{
"data": {
"id": "01JC…"
}
}
call.completed · Call finished (with summary)
{
"data": {
"caller": "+447700900123",
"called": "+441614960000",
"outcome": "booked",
"status": "completed",
"started_at": "2026-10-21T09:41:12Z",
"ended_at": "2026-10-21T09:43:40Z",
"duration_seconds": 148,
"transferred_to": null,
"summary": "Sam booked a men’s cut for Thursday at 2pm."
}
}
message.taken · Message taken for the team
{
"data": {
"id": "01JC…",
"caller_name": "Sam Taylor",
"caller_phone": "+447700900123",
"message": "Please call me back about the quote.",
"reason": "callback",
"call_id": "01JD…"
}
}
chat.completed · Website chat finished
{
"data": {
"id": "01JC…",
"status": "closed",
"outcome": "answered",
"visitor_name": "Jo",
"visitor_email": "[email protected]",
"visitor_phone": null,
"page_url": "https://example.com/prices",
"messages": 4,
"summary": "Jo asked about parking.",
"started_at": "2026-10-21T09:40:00+01:00"
}
}
outbound.completed · Outbound call finished
{
"data": {
"id": "01JC…",
"purpose": "reminder",
"to": "+447700900123",
"status": "completed",
"result": "resolved",
"attempts": 1,
"call_id": "01JD…",
"contact": {
"id": "01JC…",
"name": "Sam Taylor",
"phone": "+447700900123",
"email": null
}
}
}
keyword.spotted · Keyword alert matched
{
"data": {
"id": "01JC…",
"alert": "Complaints",
"phrase": "not happy",
"channel": "calls",
"snippet": "I’m really not happy with my crown",
"call_id": "01JD…",
"conversation_id": null
}
}