DeewanDocs
API keys

Webhooks

Signed, retried HTTPS callbacks for meeting activity.

Add an endpoint in Developers → Webhooks (or with the API). Deewan POSTs a JSON event to it whenever something you subscribed to happens.

Events

TypeWhen
room.created / room.updated / room.deletedA room changes through the API
room.startedThe first person joins a meeting
room.finishedThe meeting has emptied and closed
participant.joined / participant.leftSomeone enters or leaves
participant.stage_changedA webinar attendee is invited to speak, or sent back to the audience
meeting.scheduled / meeting.updated / meeting.cancelledA calendar meeting changes through the API
poll.created / poll.closedA poll opens or closes
breakout.started / breakout.endedBreakout rooms open or close
recording.readyA recording is complete and can be fetched
Payload
{
  "id": "evt_Vd9sX2…",
  "type": "participant.joined",
  "created_at": "2026-09-17T09:20:11.482Z",
  "data": {
    "room": { "id": "5f0c2b8e-…", "code": "k7pq-3mzt-9xwe", "name": "Onboarding call" },
    "participant": { "identity": "api_Qm9v….k2d9x0aa", "name": "Omar", "role": "participant" },
    "occurred_at": "2026-09-17T09:20:11.000Z"
  }
}

Verify every request

Each request carries Deewan-Signature: t=<unix seconds>,v1=<hex>, where v1 is HMAC-SHA256 of <t>.<raw body> with your endpoint's secret. Reject anything that doesn't match or is older than five minutes.

Next.js route with the SDK
import { verifyWebhook } from "@deewan/sdk";

export async function POST(request: Request) {
  const raw = await request.text(); // the raw body, not parsed JSON
  try {
    const event = await verifyWebhook(raw, request.headers.get("deewan-signature"), process.env.DEEWAN_WEBHOOK_SECRET!);
    if (event.type === "room.finished") await markSessionComplete(event.data);
    return new Response(null, { status: 204 });
  } catch {
    return new Response("invalid signature", { status: 400 });
  }
}
Python, without the SDK
import hmac, hashlib, time

def verify(raw_body: bytes, header: str, secret: str, tolerance=300) -> bool:
    parts = dict(p.split("=", 1) for p in header.split(","))
    t = int(parts["t"])
    if abs(time.time() - t) > tolerance:
        return False
    expected = hmac.new(secret.encode(), f"{t}.".encode() + raw_body, hashlib.sha256).hexdigest()
    return hmac.compare_digest(expected, parts["v1"])

Delivery

  • Respond with any 2xx within 10 seconds. Do slow work after responding.
  • Failed deliveries are retried from a durable queue after about 10 seconds, 1 minute, 5 minutes, 30 minutes and 2 hours, so they survive our restarts. 4xx answers (except 408 and 429) are not retried.
  • Delivery is at-least-once: use the event id to ignore duplicates.
  • Events can arrive out of order; use occurred_at.
  • Endpoints must be public https:// URLs. Private, local and cloud-metadata addresses are refused, and redirects are not followed.
  • See each endpoint's recent deliveries in the dashboard or at GET /webhooks/{id}/deliveries.