Webhooks
ReMeet sends signed POST requests to your server when something happens. Set it up in your developer account: your app → API and webhooks → address (https) and events. On the first save you get a signing secret (shown once).
Events
| Event | When | data |
|---|---|---|
app.installed | A person installed your app | user.id |
app.uninstalled | A person removed it | user.id |
meeting.started | A meeting started | meeting |
meeting.ended | It ended | meeting |
participant.joined | Someone joined | meeting, participant |
participant.left | Someone left | meeting, participant |
ping | You pressed «Send a test event» | message |
Meeting events come only for meetings whose host allowed your app meetings.read through Sign in with ReMeet.
The request
POST /remeet/webhook HTTP/1.1
Content-Type: application/json
User-Agent: ReMeet-Webhooks/1.0
ReMeet-Event: participant.joined
ReMeet-Delivery: 7d3f0c1e-1b0a-4a0f-9d55-2c3b9f4c6a10
ReMeet-Signature: t=1794067200,v1=5f2b…c9
{
"id": "7d3f0c1e-1b0a-4a0f-9d55-2c3b9f4c6a10",
"type": "participant.joined",
"created_at": "2026-11-07T12:00:00+00:00",
"app": "planning-poker",
"data": {
"meeting": { "id": "01m6saztdr6cafg9yzx83db0fs", "title": "Daily", "host_id": "p_3f9c…" },
"participant": { "id": "p_8a1d…", "name": "Omar" }
}
}All ids of people are the ids for your app (p_…), the same as in meetings and in the API.
Check the signature
v1 is HMAC-SHA256(secret, "<t>.<raw body>") in hex. Reject requests with a wrong signature or a t older than 5 minutes.
import crypto from 'node:crypto';
export function verifyReMeet(rawBody, header, secret) {
const { t, v1 } = Object.fromEntries(header.split(',').map((part) => part.split('=')));
const expected = crypto.createHmac('sha256', secret).update(`${t}.${rawBody}`).digest('hex');
const fresh = Math.abs(Date.now() / 1000 - Number(t)) < 300;
return fresh && crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(v1));
}import hashlib, hmac, time
def verify_remeet(raw_body: bytes, header: str, secret: str) -> bool:
parts = dict(part.split("=", 1) for part in header.split(","))
expected = hmac.new(secret.encode(), f"{parts['t']}.".encode() + raw_body, hashlib.sha256).hexdigest()
return abs(time.time() - int(parts["t"])) < 300 and hmac.compare_digest(expected, parts["v1"])function verifyReMeet(string $rawBody, string $header, string $secret): bool
{
parse_str(str_replace(',', '&', $header), $parts);
$expected = hash_hmac('sha256', $parts['t'].'.'.$rawBody, $secret);
return abs(time() - (int) $parts['t']) < 300 && hash_equals($expected, $parts['v1']);
}Use the raw body as it came, before parsing JSON.
Answer and retries
Answer 2xx within 10 seconds — that counts as delivered. Otherwise ReMeet repeats: after 1 minute, 5 minutes and 30 minutes (4 attempts in all). Redirects are not followed. The same event can arrive twice: deduplicate by ReMeet-Delivery.
The last deliveries and your server's answers are in your developer account under Recent deliveries.
Rules
- The address is public
https(internal and private network addresses are refused). - Do the work after answering: put the event in a queue and reply
200at once.