Groupilot Docs

Outbound webhooks

Receive events from Groupilot at your URL, the envelope, the data of each event type, the HMAC signature, retries and the endpoint API.

Groupilot can call your server when something happens in your account: a member joins, a message is sent or fails, a community is created or fills up, the number disconnects, an event is paused, a broadcast ends. Reference: Webhooks.

Two kinds of webhooks exist, with the same envelope, signature and retries:

KindScopeConfigured inEvents
Account webhooksThe whole accountSettings, Integrations, Webhooks, or POST /v1/webhooksAny of the 8 types below
Member webhookOne eventEvent settings, or PUT /v1/events/{id}/member-webhook (see Member webhook by API)member.joined only, with the ids of the guide page

Event types and their data

TypeWhendata
member.joinedSomeone joined a WhatsApp community of one of your eventsevent_id, occurrence_id, community_id, whatsapp_id, community_name, member: { phone, name? }
job.sentA scheduled message was sent, to all or part of its communitiesjob_id, event_id, occurrence_id, source (sequence or ad_hoc), sent_at, targets_to_verify
job.failedA scheduled message failed for good, no more automatic retryjob_id, event_id, occurrence_id, source, category, reason
community.createdA WhatsApp community was created for an occurrencecommunity_id, occurrence_id, event_id, whatsapp_id, name, number
community.fullA community reached its switch threshold, the next one is opensame as community.created plus member_count
channel.disconnectedThe WhatsApp number disconnectedchannel_id, source (webhook or health_monitor), detected_at
event.pausedAn event was pausedevent_id, name
broadcast.completedA broadcast finishedbroadcast_id, status (completed, failed, requires_review), success_count, failure_count, uncertain_count, community_count, sent_at
webhook.testYou sent a testtest: true

Ids are the public API ids: the same ones you get from GET /v1/events, /occurrences, /communities and /jobs.

The envelope

{
  "id": "6f1c2d0e-…",
  "type": "member.joined",
  "version": "1",
  "occurred_at": "2026-11-06T09:12:44+01:00",
  "project_id": "…",
  "data": {
    "event_id": "…",
    "occurrence_id": "…",
    "community_id": "…",
    "whatsapp_id": "1203630…@g.us",
    "community_name": "Webinar Marie - 09/11",
    "member": { "phone": "+33612345678", "name": "Julie" }
  }
}

Headers: Content-Type: application/json, X-Groupilot-Event-Id (same as id), X-Groupilot-Event-Type, X-Groupilot-Signature.

Delivery is at least once: a retry reuses the same id. Deduplicate on it.

Signature

X-Groupilot-Signature: t=1762420364,v1=5f3a…

v1 is the hex HMAC-SHA256 of t + "." + rawBody with the signing secret of the endpoint (whsec_…, shown once at creation or rotation). Reject messages whose t is older than 5 minutes.

import crypto from "node:crypto";

export function verify(rawBody, sig, secret) {
  const [t, v1] = sig.split(",").map((p) => p.split("=")[1]);
  const expected = crypto.createHmac("sha256", secret).update(`${t}.${rawBody}`).digest("hex");
  return (
    expected.length === v1.length &&
    crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(v1)) &&
    Math.abs(Date.now() / 1000 - Number(t)) < 300
  );
}

Verify on the raw body, before any JSON parsing or re-serialisation.

Retries

  • Answer 2xx within 5 seconds. Process the message afterwards.
  • Anything else (timeout, network error, non-2xx) is retried 6 more times over about 24 hours: after 1 min, 5 min, 30 min, 2 h, 6 h and 15 h.
  • A delivery has a status (pending, processing, delivered, failed, skipped), attempt_count, max_attempts, last_http_status, last_error_code and next_attempt_at. 30 days of history.
  • While an endpoint is paused (enabled: false), nothing is queued for it.
  • After 50 consecutive deliveries failed for good, the endpoint is paused automatically: enabled becomes false with disabled_reason: "failures" ("user" when you paused it yourself), and consecutive_failures counts the failures since the last success. Fix your side, then PATCH with enabled: true: deliveries resume and the counter resets.

Manage endpoints by API

read scope for the GETs, write for the rest. 5 endpoints per account. HTTPS, public host, no private address and no credentials in the URL.

POST /v1/webhooks
{ "url": "https://hooks.example.com/groupilot", "events": ["member.joined", "job.failed", "community.full"], "description": "CRM sync" }

The 201 response is the endpoint plus signing_secret, shown once.

CallDoes
GET /v1/webhooks, GET /v1/webhooks/{id}List and read
PATCH /v1/webhooks/{id}Change url, events, description; enabled: false pauses, true resumes, including after an automatic pause
DELETE /v1/webhooks/{id}Removes it with its delivery history
POST /v1/webhooks/{id}/rotate-secretNew signing_secret for the next deliveries. Update your verifier first, then rotate
POST /v1/webhooks/{id}/testPosts a webhook.test now and returns the first attempt: status (delivered, failed, pending), http_status, error_code
GET /v1/webhooks/{id}/deliveriesNewest first, limit and before to page

In the app

Settings, Integrations, Webhooks card: add an address, tick the events, copy the secret shown once, then use Test, Pause, New secret, Delete and Deliveries (last 10).

On this page