DeewanDocs
API keys

Joining meetings

Hosted links, an embedded meeting inside your app, or your own client — always end-to-end encrypted.

A join link opens Deewan's meeting page with the name, role and language you chose. The person needs no account and skips the waiting room. Links expire (default one hour, up to seven days) and are personal — don't share one link between people.

ts
const link = await deewan.rooms.createJoinLink(roomId, {
  name: user.displayName,
  role: user.isTeacher ? "host" : "participant",
  user_id: user.id,
  locale: "ar", // Arabic interface
});
return Response.redirect(link.url, 303);

Embed the meeting in your app

The same link can be placed in an <iframe>, so people stay inside your product and still get Deewan's meeting experience. Your page must allow the camera and microphone through to the frame.

html
<iframe
  src="https://deewan.io/ar/j/TICKET"
  allow="camera; microphone; display-capture; autoplay; clipboard-write; picture-in-picture"
  style="width: 100%; height: 100%; border: 0"
></iframe>

Only join links can be embedded, never the dashboard or a room's own address. Each frame carries one person's signed, expiring link, so an embedded meeting is no easier to get into than the link itself.

Custom client (your own interface)

A media token is credentials for one person for one meeting. Connect with @deewan/client, which handles end-to-end encryption and gives you participants, tracks and events to render however you like.

Your server
const credentials = await deewan.rooms.createToken(roomId, {
  name: "Omar",
  user_id: "user_456",
});
// Send { server_url, token, e2ee_passphrase } to that one user's client.
bash
npm install @deewan/client
In the browser
import { joinMeeting } from "@deewan/client";

const c = await fetch("/api/meeting-token").then((r) => r.json());

const meeting = await joinMeeting(
  { serverUrl: c.server_url, token: c.token, e2eePassphrase: c.e2ee_passphrase },
  { publish: { camera: true, microphone: true } },
);

meeting.on("participants", (people) => renderTiles(people));
meeting.on("trackSubscribed", ({ attach, kind }) => {
  const el = document.createElement(kind === "video" ? "video" : "audio");
  el.autoplay = true;
  el.playsInline = true;
  attach(el);
  stage.append(el);
});

// Chat and reactions travel end-to-end encrypted, like the media.
meeting.on("message", ({ name, text }) => showChat(name, text));
meeting.on("reaction", ({ name, emoji }) => floatEmoji(name, emoji));
await meeting.sendMessage("Hello everyone");
await meeting.sendReaction("👏");

Tokens are valid for 10 minutes to connect; an active connection stays up. Request a fresh token for each join. Without the passphrase a client receives only noise — that is the encryption working.

Roles

hostparticipant
Camera and microphoneYesYes
Screen share when the room says hosts-onlyYesNo
Chat and reactions when turned offCan still postNo
Start a recording (Deewan's interface)YesNo
Create polls (Deewan's interface)YesNo

Moderation (muting, removing, ending, the waiting room) is done from your server with the participants endpoints, or by a signed-in Deewan host inside the meeting.

Co-hosts and the waiting room
// Host powers for this meeting only; Deewan accounts only.
await deewan.rooms.makeCohost(roomId, identity);
await deewan.rooms.removeCohost(roomId, identity);

// Let people in (or not) from your own dashboard.
const { data: waiting } = await deewan.rooms.listWaiting(roomId);
for (const person of waiting) await deewan.rooms.admit(roomId, person.identity);

Rooms with strict encryption keep their key on participants' devices only, so POST /rooms/{id}/tokens answers 409 for them — there is no passphrase to give. Send people to the room with a join link instead.

Locked rooms

While a room is locked, new links and tokens are refused with 409 conflict, and people can't enter with existing links. Hosts can still join to unlock it.