DNZ Voice Docs
Webhooks & Events
Set webhook URL per app in the console. DNZ POSTs JSON events to your endpoint.
Configure
Console → Apps → select app → Webhook URL.
PATCH /v1/apps/{appId}/webhook with JWT from developer login.
The response returns webhookSecret (dnz_whsec_…) — save it, you need it to verify signatures. It is also returned by GET /v1/apps.
PATCH https://voice-api.dnzteam.online/v1/apps/app_xxxx/webhook
Authorization: Bearer <developer_jwt>
{ "webhookUrl": "https://your-server.com/voice-events" }
→ { "ok": true, "appId": "app_xxxx", "webhookUrl": "...", "webhookSecret": "dnz_whsec_..." }Event payload
room.started — { roomId }
room.ended — { roomId }
peer.joined / peer.left — { roomId, peerId, uid }
{
"event": "room.started",
"appId": "app_abc",
"payload": { "roomId": "class_101" },
"ts": "2026-07-01T12:00:00.000Z"
}Verify the signature (required in production)
Every request includes X-DNZ-Timestamp and X-DNZ-Signature: sha256=<hex>.
Compute HMAC-SHA256 of `${timestamp}.${rawBody}` using your webhookSecret and compare with the signature (constant-time compare).
Reject requests where the timestamp is more than a few minutes old to prevent replay.
import crypto from "crypto";
function verifyDnzWebhook(rawBody, timestamp, signatureHeader, secret) {
const expected = crypto
.createHmac("sha256", secret)
.update(`${timestamp}.${rawBody}`)
.digest("hex");
const provided = signatureHeader.replace("sha256=", "");
return crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(provided));
}