Receive HTTP POST callbacks when things happen — roster changes, game lifecycle, scheduled reminders, RSVP and payment moments. Payloads are lean domain facts; your client renders them.
| Type | Triggered when |
|---|---|
player_joined | A player is added to the event |
player_left | A player is removed from the event |
player_invited | A player is invited to the event |
game_full | The last active spot is filled (spotsLeft = 0) |
game_reset | A recurring event resets its player list |
game_cancelled | An organizer cancels the current game |
game_payer_set | A payer is set (or changed) on a game that has already ended |
game_scheduled | The event is created |
game_rescheduled | The date/time, court/location or duration changes |
reminder | A pre-game reminder fires (one of the event's enabled Reminder Timings) |
rsvp_request | The T-48h attendance fanout asks players to confirm |
rsvp_summary | The T-24h attendance summary is computed for the organizer |
recruitment | A recruitment ping fires for a non-full game (T-48h, or urgent at T-24h) |
payment_nudge | A debtor is nudged about an unpaid game (escalation stages 1–3) |
payment_organizer_alert | Stage-3 nudges expired — the organizer is alerted about chronic non-payers |
payer_check_in | The payer is asked whether everyone has paid (24h after the game) |
When a recurring event is cancelled, game_reset is deliveredbefore game_cancelled. By the timegame_cancelled arrives, the event's current game is already thenext occurrence — so game_cancelled.dateTime is thecancelled game's start, not the current game's. UsegameId to tell them apart.
Subscribe a webhook URL to receive event notifications.
| Field | Type | Required | Description |
|---|---|---|---|
url | string | Yes | The URL to receive POST callbacks |
events | string[] | No | Event types to subscribe to. Empty array = all events. An unknown type returns 400 — it is never silently dropped (which would mean "all events") |
secret | string | No | Shared secret for HMAC-SHA256 signature verification |
curl -X POST https://convocados.fly.dev/api/events/EVENT_ID/webhooks \
-H "Content-Type: application/json" \
-d '{
"url": "https://example.com/webhook",
"events": ["player_joined", "game_full"],
"secret": "my-shared-secret"
}'{
"id": "cmm...",
"url": "https://example.com/webhook",
"events": ["player_joined", "game_full"],
"createdAt": "2026-03-15T13:00:00.000Z"
}409List all webhook subscriptions for an event.
{
"webhooks": [
{
"id": "cmm...",
"url": "https://example.com/webhook",
"events": ["player_joined", "game_full"],
"createdAt": "2026-03-15T13:00:00.000Z"
}
]
}Unsubscribe a webhook.
curl -X DELETE https://convocados.fly.dev/api/events/EVENT_ID/webhooks/WEBHOOK_ID{ "ok": true }When an event occurs, Convocados sends a POST request to your URL with this JSON body:
{
"event": "player_joined",
"eventId": "clx...",
"deliveryId": "550e8400-e29b-41d4-a716-446655440000",
"timestamp": "2026-03-15T13:30:00.000Z",
"data": {
"playerName": "Carlos",
"isActive": true,
"spotsLeft": 3,
"actor": "Carlos"
}
}| Event type | data fields |
|---|---|
player_joined | playerName, isActive, spotsLeft, actor (who joined: the player/user name or "anonymous") |
player_left | playerName, spotsLeft, actor (who removed them: a player/user name or "anonymous") |
player_invited | playerName, inviteUrl (the claim link the invitee must open) |
game_full | playerName, isActive, spotsLeft, actor (always 0) |
game_reset | newDateTime (ISO 8601) |
game_cancelled | gameId (the cancelled game — dedupe key for the concurrent-cancel race), dateTime (ISO 8601, the cancelled game's start, not the current game's), isRecurring |
game_payer_set | gameId, dateTime (ISO 8601), payerName (who to pay), payerEventPlayerId (the payer's roster id, or null for an external payer), payerUserId (the payer's account id when linked, else null), paymentMethods (array of payment-method objects — type, value, optional label — how to pay them), amountPerPlayer (per-player share), currency |
game_scheduled | — (lean fact; fetch the event for details) |
game_rescheduled | — (lean fact; fetch the event for current date/location/duration) |
reminder | reminderType ("24h" | "2h" | "1h"), gameId, dateTime (ISO 8601, the occurrence's start) |
rsvp_request | gameId, dateTime (ISO 8601) |
rsvp_summary | yes, no, pending (attendance counts) |
recruitment | spotsLeft, urgent (false at T-48h, true at T-24h) |
payment_nudge | stage (1 = soft nudge, 2 = follow-up, 3 = social proof), amountCents, currency, playerName (debtor — only present when the event has showDebtorNames enabled) |
payment_organizer_alert | unpaidCount, unpaidNames (only present when showDebtorNames is enabled) |
payer_check_in | gameId, dateTime (ISO 8601), amountCents (outstanding), currency |
If you provided a secret when subscribing, each delivery includes anX-Webhook-Signature header with an HMAC-SHA256 signature:
X-Webhook-Signature: sha256=a1b2c3d4e5f6...To verify in Node.js:
import { createHmac } from "crypto";
function verify(payload, signature, secret) {
const expected = "sha256=" + createHmac("sha256", secret)
.update(payload)
.digest("hex");
return expected === signature;
}| Header | Value |
|---|---|
Content-Type | application/json |
User-Agent | Convocados-Webhook/1.0 |
X-Webhook-Signature | HMAC-SHA256 (only if secret configured) |