Skip to content

Webhooks

Webhooks push workspace events to an HTTPS endpoint you host, as they happen. Workspace owners manage endpoints in the application under Settings → Webhooks — add a URL, copy the signing secret (shown exactly once), and events start flowing.

Software can manage endpoints too, with a credential granted the workspace.webhooks:write scope (reading needs workspace.webhooks:read; a personal access token must also belong to a workspace owner):

Terminal window
curl -X POST \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Idempotency-Key: $(uuidgen)" \
-H "Content-Type: application/json" \
-d '{ "url": "https://example.com/hooks" }' \
https://api.reminix.com/v1/workspace/webhook-endpoints

The response carries the endpoint’s signing secret — once. The API also lists endpoints, re-enables one after repeated failures, sends a test event, and lists and redelivers deliveries; see the API reference.

AI agents connected to the workspace can read endpoints and delivery history, send test events and redeliver — but not add, remove or re-enable endpoints: those change where your workspace’s data is sent, so a person configures them.

Each delivery is an HTTP POST with a JSON body:

{
"id": "evt_5f0c…",
"type": "workspace.member.joined",
"createdAt": "2026-08-20T12:00:00.000Z",
"data": { "userId": "…", "email": "casey@example.com" }
}

and three signature headers:

webhook-id: del_9a1b…
webhook-timestamp: 1755691200
webhook-signature: v1,MEQCIB…
  • webhook-id identifies this delivery. It stays the same across retries — use it to deduplicate.
  • id inside the body identifies the event. If you registered multiple endpoints, each receives its own delivery of the same event — deduplicate across endpoints by event id if you need to.

Deliveries are signed with your endpoint’s secret (whsec_…) using the same scheme as Svix, so any standard Svix library verifies them:

import { Webhook } from "svix";
const wh = new Webhook(process.env.WEBHOOK_SECRET);
// Express-style handler; `payload` must be the RAW request body string.
app.post("/webhooks", (req, res) => {
let event;
try {
event = wh.verify(req.body, req.headers);
} catch {
return res.status(400).send("bad signature");
}
// handle event…
res.status(200).send("ok");
});

Verifying by hand: the signature is v1, followed by a Base64 HMAC-SHA256 of `${webhookId}.${timestamp}.${body}` keyed with the secret after its whsec_ prefix (Base64-decoded). Always verify against the raw body — re-serializing JSON breaks the signature — and reject timestamps older than a few minutes to prevent replays.

Return a 2xx within 10 seconds. If your processing is slow, acknowledge first and process asynchronously — a timeout counts as a failed delivery.

Failed deliveries (non-2xx, or timeout) retry automatically with backoff, up to 6 attempts, with the same webhook-id. An endpoint that fails 20 consecutive deliveries is disabled automatically — delete and re-add it (new secret) once your endpoint is healthy.

Under Settings → Webhooks → Deliveries you can see each delivery’s status and attempts, send a test event (type: "ping"), and redeliver any recorded delivery — a redelivery arrives with a fresh webhook-id but the same event id, so event-level deduplication still applies.

Type Fires when data
workspace.member.joined An invitation is accepted. userId, email
ping You send a test from Settings. a test message

Event payloads only ever gain fields — build tolerant parsers. GET /v1/workspace/event-types returns the full list, with what each means.

An endpoint receives every event unless you choose: in Settings → Webhooks pick “Only these” when adding it, or pass eventTypes when creating it through the API — only listed types are accepted:

Terminal window
curl -X POST https://api.reminix.com/v1/workspace/webhook-endpoints \
-H "Authorization: Bearer YOUR_API_KEY" -H "content-type: application/json" \
-d '{"url":"https://example.com/hooks","eventTypes":["workspace.member.joined"]}'

A test event (ping) always reaches the endpoint you test.