Webhooks let Crew tell your own systems when something happens, instead of you asking the API over and over. Crew sends a signed JSON POST to your HTTPS address within seconds of the event.

Adding a webhook

Super Users open Settings → Integrations → Webhooks → Add webhook and enter:

  • the URL: a public https:// address. Addresses with a username or password, IP addresses and internal hostnames aren't accepted.
  • the Events to send (see below).

Crew creates a signing secret (starting with whsec_). Copy it into your receiving system; you need it to check that deliveries really come from Crew. An organization can have up to 10 webhooks.

Click Test to send a ping event straight away, and Deliveries to see recent attempts, their status code and how many tries they took.

Events

Event Sent when data
employee.created A member is added (by hand, import, automatically or by accepting an application). The member (see below).
employee.updated A member's profile or role changes. The member.
employee.deleted A member is deleted. { "id", "name" }, or { "id", "erased": true } after a GDPR deletion.
request.submitted A member submits a request. { "id", "number", "type", "employeeId" }
request.decided A request is approved or rejected. { "id", "number", "type", "decision", "employeeId" }
announcement.published An announcement is posted. { "id", "title", "departmentId" }
ping You click Test. { "message": "Hello from Crew" }

The member object has id, name, title, departmentId, unitId, managerId, employmentStatus and discordId. Like the API, webhooks never include contact details, bios, notes, disciplinary records or leave reasons.

What a delivery looks like

POST /your/endpoint HTTP/1.1
Content-Type: application/json
User-Agent: Crew-Webhooks/1
X-Crew-Event: request.decided
X-Crew-Signature: t=1793030400,v1=5d41402abc4b2a76b9719d911017c592...

{
  "id": "6a1f0d3e-7c2b-4f9a-8e5d-3b1c9a7e2f40",
  "event": "request.decided",
  "guildId": "123456789012345678",
  "occurredAt": "2026-10-26T14:00:00.000Z",
  "data": { "id": "b2e4f6a8-1c3d-4e5f-8a9b-0c1d2e3f4a5b", "number": "REQCREW000042", "type": "leave", "decision": "approved", "employeeId": "1001" }
}

id is unique per event: use it to ignore an event you've already handled (a retry can deliver the same event twice).

Checking the signature

Always check that a delivery came from Crew before trusting it:

  1. Read t (a Unix time in seconds) and v1 from the X-Crew-Signature header.
  2. Compute HMAC-SHA256 of <t>.<raw request body> using your signing secret, as lowercase hex.
  3. Compare it with v1 using a constant-time comparison.
  4. Reject the delivery if t is more than about 5 minutes old, so a captured request can't be replayed later.

Use the raw body exactly as received, before any JSON parsing.

// Node.js (Express): app.post("/crew-webhook", express.raw({ type: "application/json" }), handler)
import { createHmac, timingSafeEqual } from "node:crypto";

function verifyCrewWebhook(rawBody, header, secret) {
  const parts = Object.fromEntries(header.split(",").map((p) => p.split("=")));
  const t = Number(parts.t);
  if (!t || Math.abs(Date.now() / 1000 - t) > 300) return false; // too old
  const expected = createHmac("sha256", secret).update(`${t}.${rawBody}`).digest("hex");
  const given = Buffer.from(parts.v1 ?? "", "hex");
  const wanted = Buffer.from(expected, "hex");
  return given.length === wanted.length && timingSafeEqual(given, wanted);
}

Responding, retries and pausing

  • Answer with any 2xx status within 10 seconds. Do slow work after answering.
  • Redirects aren't followed, and count as failures.
  • A failed delivery is retried after about 1 minute, 5 minutes, 30 minutes, 2 hours and 12 hours: six attempts over roughly 15 hours. After that it's abandoned.
  • After 10 failed deliveries in a row, the webhook is paused. Fix your endpoint, then turn it back on in the webhook list.

Deliveries are kept for 7 days.

Warning: Webhooks send organization data to a system you control, and your organization is responsible for it. Treat the signing secret like a password. If it leaks, delete the webhook and add it again to get a new secret.

Still need a hand?

Open a ticket in our Discord, and our team will help you out.

Open a ticket