Qarar

Webhooks and Slack or Teams posts

Qarar sends a signed event to an address you choose when a meeting finishes, its notes are ready or a task changes, and posts a formatted recap to a Slack or Teams channel. None of it needs a marketplace app or OAuth permissions: you paste an address into Settings.

Events5 eventsMeeting completed, notes ready, and a task created, completed or assigned.
SignatureHMAC-SHA256With a timestamp, under a per-endpoint secret shown once.
Retries8 attemptsOver about 45 hours, with a log of the last 50 deliveries.
Timeout10 secondsRedirects are not followed, and at most 64 KB of the response is read.

Setting up

Workspace owners and admins add destinations in Settings → Integrations. Other members do not see the page, and every creation, change, secret rotation and deletion is recorded in the workspace’s activity log.

Each workspace holds up to 10 destinations of three kinds: a signed webhook (webhook), a Slack Incoming Webhook URL (slack), and a Microsoft Teams Workflows URL (teams). For each one you choose the events it gets, the content level, and the language of Slack and Teams messages.

When you create a webhook, its signing secret (whsec_…) is shown once only; keep it in your secret store. Rotate secret makes a new one, and the old one keeps signing for 24 hours so you can update your server without missing an event.

Send test sends one ping event and shows the result straight away; Disable stops sending without deleting the destination. The delivery log shows the last 50 attempts with their state, response code and duration.

Zapier, Make and n8n

Zapier
Start a Zap with Webhooks by Zapier → Catch Hook, paste the address it gives you into a webhook destination, then press Send test so Zapier learns the fields.
Make
Start the scenario with Webhooks → Custom webhook, copy its address, then send a test event so Make determines the data structure.
n8n
Add a Webhook node with the POST method and use its Production URL, not the test URL. A self-hosted n8n must be reachable from the internet over HTTPS on port 443.

These tools do not check the signature on their own. If you need that, add a code step using the Node or Python example below, or treat the address itself as a secret you never share.

Events

EventWhen it is sent
meeting.completedWhen the meeting’s transcript is complete. The notes may follow a few minutes later in a notes.ready event.
notes.readyWhen the meeting’s notes are written, and again each time they are rewritten (after a speaker is named or the notes settings change).
task.createdFor each new task that appears in the notes.
task.completedWhen someone marks a task done.
task.assignedWhen a task is assigned to a workspace member. Unassigning sends no event.
pingFrom the Send test button only, and never retried.

Payloads

Every event is a UTF-8 JSON object with the same envelope: id, type, api_version, created (Unix seconds) and workspace, then the event’s data. Arabic text arrives as it is, without \u escapes. Times are ISO 8601 in UTC, and due_date is YYYY-MM-DD.

In a task, owner is the name as the notes give it, assignee is the workspace member it is assigned to, deadline is the due time as said in the meeting, and timestamp is when it was said in the recording. The url is the meeting’s page in the app (under /en/ for an English destination), and only someone with a Qarar account and access to the meeting can open it.

meeting.completed
Example · JSON
{
  "id": "evt_1a1a1a1a1a1a1a1a1a1a1a1a",
  "type": "meeting.completed",
  "api_version": "2026-09-27",
  "created": 1790503934,
  "workspace": {
    "id": "ws_7d20e57d20e57d20e57d20e5",
    "name": "Studio Nour"
  },
  "data": {
    "meeting": {
      "id": "mt_4f1c9a2b4f1c9a2b4f1c9a2b4f1c9a2b",
      "title": "مراجعة خطة الإطلاق",
      "url": "https://getqarar.com/m/mt_4f1c9a2b4f1c9a2b4f1c9a2b4f1c9a2b",
      "source": "upload",
      "recorded_at": "2026-09-27T09:00:00Z",
      "completed_at": "2026-09-27T09:52:14Z",
      "duration_seconds": 2710
    }
  }
}
notes.ready
Example · JSON
{
  "id": "evt_2b2b2b2b2b2b2b2b2b2b2b2b",
  "type": "notes.ready",
  "api_version": "2026-09-27",
  "created": 1790503934,
  "workspace": {
    "id": "ws_7d20e57d20e57d20e57d20e5",
    "name": "Studio Nour"
  },
  "data": {
    "meeting": {
      "id": "mt_4f1c9a2b4f1c9a2b4f1c9a2b4f1c9a2b",
      "title": "مراجعة خطة الإطلاق",
      "url": "https://getqarar.com/m/mt_4f1c9a2b4f1c9a2b4f1c9a2b4f1c9a2b",
      "source": "upload",
      "recorded_at": "2026-09-27T09:00:00Z",
      "completed_at": "2026-09-27T09:52:14Z",
      "duration_seconds": 2710
    },
    "notes": {
      "language": "ar",
      "summary": [
        "راجع الفريق خطة الإطلاق في السعودية واتفق على موعد نهاية الربع.",
        "ميزانية الإعلانات أعلى من المتوقع وتحتاج إلى موافقة."
      ],
      "decisions": [
        "اعتماد ميزانية الإعلانات في السعودية للربع القادم."
      ],
      "tasks": [
        {
          "id": "tk_a3b4c5d6a3b4c5d6a3b4c5d6",
          "text": "إرسال الخطة التفصيلية إلى الفريق",
          "owner": "لينا",
          "assignee": {
            "email": "lina@noor.example"
          },
          "due_date": "2026-10-01",
          "deadline": "غدًا صباحًا",
          "status": "open",
          "timestamp": "00:46",
          "url": "https://getqarar.com/m/mt_4f1c9a2b4f1c9a2b4f1c9a2b4f1c9a2b#t=46"
        },
        {
          "id": "tk_e7f8a9b0e7f8a9b0e7f8a9b0",
          "text": "إصلاح خلل صفحة الدفع",
          "owner": "كريم",
          "assignee": null,
          "due_date": null,
          "deadline": "قبل الخميس",
          "status": "open",
          "timestamp": "00:23",
          "url": "https://getqarar.com/m/mt_4f1c9a2b4f1c9a2b4f1c9a2b4f1c9a2b#t=23"
        }
      ]
    }
  }
}
task.created
Example · JSON
{
  "id": "evt_3c3c3c3c3c3c3c3c3c3c3c3c",
  "type": "task.created",
  "api_version": "2026-09-27",
  "created": 1790503934,
  "workspace": {
    "id": "ws_7d20e57d20e57d20e57d20e5",
    "name": "Studio Nour"
  },
  "data": {
    "meeting": {
      "id": "mt_4f1c9a2b4f1c9a2b4f1c9a2b4f1c9a2b",
      "title": "مراجعة خطة الإطلاق",
      "url": "https://getqarar.com/m/mt_4f1c9a2b4f1c9a2b4f1c9a2b4f1c9a2b",
      "source": "upload",
      "recorded_at": "2026-09-27T09:00:00Z",
      "completed_at": "2026-09-27T09:52:14Z",
      "duration_seconds": 2710
    },
    "task": {
      "id": "tk_a3b4c5d6a3b4c5d6a3b4c5d6",
      "text": "إرسال الخطة التفصيلية إلى الفريق",
      "owner": "لينا",
      "assignee": null,
      "due_date": null,
      "deadline": "غدًا صباحًا",
      "status": "open",
      "timestamp": "00:46",
      "url": "https://getqarar.com/m/mt_4f1c9a2b4f1c9a2b4f1c9a2b4f1c9a2b#t=46"
    }
  }
}
task.completed
Example · JSON
{
  "id": "evt_4d4d4d4d4d4d4d4d4d4d4d4d",
  "type": "task.completed",
  "api_version": "2026-09-27",
  "created": 1790503934,
  "workspace": {
    "id": "ws_7d20e57d20e57d20e57d20e5",
    "name": "Studio Nour"
  },
  "data": {
    "meeting": {
      "id": "mt_4f1c9a2b4f1c9a2b4f1c9a2b4f1c9a2b",
      "title": "مراجعة خطة الإطلاق",
      "url": "https://getqarar.com/m/mt_4f1c9a2b4f1c9a2b4f1c9a2b4f1c9a2b",
      "source": "upload",
      "recorded_at": "2026-09-27T09:00:00Z",
      "completed_at": "2026-09-27T09:52:14Z",
      "duration_seconds": 2710
    },
    "task": {
      "id": "tk_a3b4c5d6a3b4c5d6a3b4c5d6",
      "text": "إرسال الخطة التفصيلية إلى الفريق",
      "owner": "لينا",
      "assignee": {
        "email": "lina@noor.example"
      },
      "due_date": "2026-10-01",
      "deadline": "غدًا صباحًا",
      "status": "done",
      "timestamp": "00:46",
      "url": "https://getqarar.com/m/mt_4f1c9a2b4f1c9a2b4f1c9a2b4f1c9a2b#t=46"
    },
    "completed_by": {
      "email": "lina@noor.example"
    }
  }
}
task.assigned
Example · JSON
{
  "id": "evt_5e5e5e5e5e5e5e5e5e5e5e5e",
  "type": "task.assigned",
  "api_version": "2026-09-27",
  "created": 1790503934,
  "workspace": {
    "id": "ws_7d20e57d20e57d20e57d20e5",
    "name": "Studio Nour"
  },
  "data": {
    "meeting": {
      "id": "mt_4f1c9a2b4f1c9a2b4f1c9a2b4f1c9a2b",
      "title": "مراجعة خطة الإطلاق",
      "url": "https://getqarar.com/m/mt_4f1c9a2b4f1c9a2b4f1c9a2b4f1c9a2b",
      "source": "upload",
      "recorded_at": "2026-09-27T09:00:00Z",
      "completed_at": "2026-09-27T09:52:14Z",
      "duration_seconds": 2710
    },
    "task": {
      "id": "tk_a3b4c5d6a3b4c5d6a3b4c5d6",
      "text": "إرسال الخطة التفصيلية إلى الفريق",
      "owner": "لينا",
      "assignee": {
        "email": "lina@noor.example"
      },
      "due_date": "2026-10-01",
      "deadline": "غدًا صباحًا",
      "status": "open",
      "timestamp": "00:46",
      "url": "https://getqarar.com/m/mt_4f1c9a2b4f1c9a2b4f1c9a2b4f1c9a2b#t=46"
    },
    "assigned_by": {
      "email": "mona@noor.example"
    }
  }
}
ping
Example · JSON
{
  "id": "evt_6f6f6f6f6f6f6f6f6f6f6f6f",
  "type": "ping",
  "api_version": "2026-09-27",
  "created": 1790503934,
  "workspace": {
    "id": "ws_7d20e57d20e57d20e57d20e5",
    "name": "Studio Nour"
  },
  "data": {
    "destination": {
      "id": "dst_0c1d2e3f0c1d2e3f0c1d2e3f",
      "name": "Zapier"
    },
    "message": "Qarar can reach this endpoint."
  }
}

What is sent, and what never is

Adding a destination moves your meetings’ content to a third party you choose: Zapier, Slack, Microsoft or your own server. Every event from the workspace’s meetings reaches its destinations, whoever created the meeting; meetings in your personal workspace reach only your personal workspace’s destinations. So each destination has one of two levels:

Full content (full)
The meeting title, the summary points, the decisions, the task text and owner names as the notes give them, the email addresses of the assignee and of whoever completed or assigned a task, and the workspace name.
Minimal (minimal)
Only ids, links, times, duration, status, due dates and counts: no titles, text, names or email addresses. Suits an alert that sends people back to Qarar.

Never sent: the transcript text, audio, voice prints, attendee email addresses from the calendar, and share links. No destination ever gets a meeting from another workspace.

The same event at the minimal level: notes.ready
Example · JSON
{
  "id": "evt_2b2b2b2b2b2b2b2b2b2b2b2b",
  "type": "notes.ready",
  "api_version": "2026-09-27",
  "created": 1790503934,
  "workspace": {
    "id": "ws_7d20e57d20e57d20e57d20e5"
  },
  "data": {
    "meeting": {
      "id": "mt_4f1c9a2b4f1c9a2b4f1c9a2b4f1c9a2b",
      "url": "https://getqarar.com/m/mt_4f1c9a2b4f1c9a2b4f1c9a2b4f1c9a2b",
      "completed_at": "2026-09-27T09:52:14Z",
      "duration_seconds": 2710
    },
    "notes": {
      "language": "ar",
      "summary_count": 2,
      "decisions_count": 1,
      "tasks_count": 2
    }
  }
}
The same event at the minimal level: task.completed
Example · JSON
{
  "id": "evt_4d4d4d4d4d4d4d4d4d4d4d4d",
  "type": "task.completed",
  "api_version": "2026-09-27",
  "created": 1790503934,
  "workspace": {
    "id": "ws_7d20e57d20e57d20e57d20e5"
  },
  "data": {
    "meeting": {
      "id": "mt_4f1c9a2b4f1c9a2b4f1c9a2b4f1c9a2b",
      "url": "https://getqarar.com/m/mt_4f1c9a2b4f1c9a2b4f1c9a2b4f1c9a2b",
      "completed_at": "2026-09-27T09:52:14Z",
      "duration_seconds": 2710
    },
    "task": {
      "id": "tk_a3b4c5d6a3b4c5d6a3b4c5d6",
      "status": "done",
      "due_date": "2026-10-01",
      "url": "https://getqarar.com/m/mt_4f1c9a2b4f1c9a2b4f1c9a2b4f1c9a2b#t=46"
    }
  }
}

Headers

HeaderValue
Content-Typeapplication/json; charset=utf-8
User-AgentQarar-Webhooks/1 (+https://getqarar.com/en/developers/webhooks/)
Qarar-Event-IdThe event id (evt_…), the same as id in the payload. It stays the same across attempts.
Qarar-Event-TypeThe event type, such as notes.ready.
Qarar-Delivery-IdThe delivery id (dl_…), as the delivery log shows it.
Qarar-SignatureThe timestamp and signature, for example: t=1790503934,v1=b833a37f88eed2326856a6484b01e3be64888c6c5c07f57f2f566c5015e08b1d

Verifying signatures

  1. Split the Qarar-Signature header at its commas: t is when it was sent, in Unix seconds, and v1 is a hex signature. For 24 hours after a secret rotation the header carries two v1 values, one per secret.
  2. Compute HMAC-SHA256 with the whole secret as it is (whsec_…) as the key, over t, a full stop, and the raw request body exactly as it arrived, before parsing or reformatting the JSON.
  3. Compare your result with each v1 value in constant time, and accept the request if one matches. Reject it if t is more than 5 minutes from your clock, so a captured request cannot be replayed later.
Node.js
import { createHmac, timingSafeEqual } from 'node:crypto';

// rawBody: the exact bytes Qarar sent (a Buffer or a string), before any JSON parsing.
export function verifyQararSignature(rawBody, header, secret, toleranceSeconds = 300) {
  const parts = String(header).split(',').map(part => part.trim().split('='));
  const t = Number(parts.find(([key]) => key === 't')?.[1]);
  const signatures = parts.filter(([key]) => key === 'v1').map(([, value]) => value);
  if (!Number.isInteger(t) || !signatures.length) return false;
  if (Math.abs(Date.now() / 1000 - t) > toleranceSeconds) return false;
  const expected = createHmac('sha256', secret).update(`${t}.`).update(rawBody).digest();
  return signatures.some(signature => {
    const given = Buffer.from(signature, 'hex');
    return given.length === expected.length && timingSafeEqual(given, expected);
  });
}

import { createServer } from 'node:http';

createServer(async (request, response) => {
  const chunks = [];
  for await (const chunk of request) chunks.push(chunk);
  const rawBody = Buffer.concat(chunks);
  if (!verifyQararSignature(rawBody, request.headers['qarar-signature'] ?? '', process.env.QARAR_WEBHOOK_SECRET)) {
    response.writeHead(400).end();
    return;
  }
  const event = JSON.parse(rawBody);
  // Skip event.id if you have already handled it, then act on event.type.
  response.writeHead(200).end();
}).listen(8080);
Python
import hashlib
import hmac
import time


def verify_qarar_signature(raw_body: bytes, header: str, secret: str, tolerance: int = 300) -> bool:
    items = [part.strip().split("=", 1) for part in header.split(",") if "=" in part]
    timestamps = [value for key, value in items if key == "t"]
    signatures = [value for key, value in items if key == "v1"]
    if not timestamps or not signatures or not timestamps[0].isdigit():
        return False
    t = int(timestamps[0])
    if abs(time.time() - t) > tolerance:
        return False
    expected = hmac.new(secret.encode(), f"{t}.".encode() + raw_body, hashlib.sha256).hexdigest()
    return any(hmac.compare_digest(expected, signature) for signature in signatures)

In Flask use request.get_data(), in Django request.body, and in FastAPI await request.body(): the raw body, not the parsed object.

A worked example to test against: this secret, timestamp and body must give the same signature.

Test vector
secret    whsec_0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef
t         1790503934
body      {"id":"evt_6f6f6f6f6f6f6f6f6f6f6f6f","type":"ping","api_version":"2026-09-27","created":1790503934,"workspace":{"id":"ws_7d20e57d20e57d20e57d20e5","name":"Studio Nour"},"data":{"destination":{"id":"dst_0c1d2e3f0c1d2e3f0c1d2e3f","name":"Zapier"},"message":"Qarar can reach this endpoint."}}
signature b833a37f88eed2326856a6484b01e3be64888c6c5c07f57f2f566c5015e08b1d

Delivery and retries

An attempt succeeds when your address answers 2xx within 10 seconds. Answer quickly and do slow work after responding.

ResponseWhat happens
2xxDelivered.
408, 429, 5xx, timeouts and network errorsRetried after 1 minute, 5 minutes, 30 minutes, 2 hours, 6 hours, 12 hours and 24 hours: 8 attempts over about 45 hours.
410 GoneThe attempt fails and the destination is disabled.
Any other 4xxFails without a retry.
3xxThe redirect is not followed, and the attempt counts as failed.
  • When 10 events in a row fail for good, the destination is disabled, and Settings shows why until you turn it back on.
  • The same event can arrive more than once (after a timeout while your server did succeed, for example), so skip any Qarar-Event-Id you have already handled.
  • Order is not guaranteed: a task.created can arrive before its notes.ready. Rely on created and on the state in the payload.
  • Each destination keeps its last 50 deliveries in its log.

Rules for destination addresses

Qarar checks the address when you save it and before every attempt, so it cannot be used to reach internal networks:

  • HTTPS only, on port 443, with no user name or password in the address.
  • The host name must resolve only to public addresses. Private, loopback, link-local, CGNAT, multicast and documentation addresses are refused, whether written as numbers or returned by DNS.
  • localhost, names ending in .local or .internal, cloud metadata host names and Qarar’s own domain are refused.
  • A Slack address must start with https://hooks.slack.com/services/, and a Teams address must be on *.webhook.office.com, *.logic.azure.com or *.powerplatform.com.

Slack

In Slack, create an app for your workspace and turn on Incoming Webhooks, or use one you have, then choose Add New Webhook to Workspace and the channel, and copy the address starting with https://hooks.slack.com/services/. Qarar needs no app in the Slack Marketplace.

The message uses Block Kit: the meeting title, summary points, decisions, tasks with their assignee and due date, and a button that opens the meeting. Arabic lines start with a right-to-left mark (U+200F) so they display right to left.

Microsoft Teams

Microsoft is retiring Microsoft 365 connectors (Office 365 Connectors); the replacement is the Workflows app. In the channel choose … → Workflows, start from the Send webhook alerts to a channel template or the When a Teams webhook request is received trigger, save, and copy the webhook address.

The post is an Adaptive Card (version 1.5, right to left when the language is Arabic) with the summary, decisions, tasks and an Open the meeting button. Workflows addresses accept this format, as do legacy connector addresses while they still work. A workflow belongs to whoever created it, so add a co-owner so it keeps running if they leave.