Webhook Management API
Create, inspect, tune, and replay your webhook subscription — all server-to-server with the same X-Game-Secret-Key you already use. No dashboard login required.
Authentication
Every endpoint below accepts your game’s X-Game-Secret-Key: ivsdk_… — the canonical server-to-server credential, identical to every other Invo API call. (The partner dashboard uses a session token for the same operations; you don’t need it.) The key’s game must be active, and a key can only manage its own game’s webhooks.
Endpoint summary
Base path: /api/dev/webhooks
| Method | Path | Purpose |
|---|---|---|
| GET | /supported-events | List every event type the platform emits |
| GET | /games/{game_id} | Current subscription + recent deliveries |
| PUT | /games/{game_id} | Create or update the subscription |
| DELETE | /games/{game_id} | Disable (soft-delete) the subscription |
| POST | /games/{game_id}/rotate-secret | Rotate the signing secret (7-day grace) |
| POST | /games/{game_id}/test | Fire a synthetic webhook.test event |
| GET | /games/{game_id}/metrics | Delivery metrics + health state |
| PATCH | /games/{game_id}/retry-policy | Per-subscription tuning overrides |
| GET | /games/{game_id}/deliveries | Paginated delivery history |
| GET | /deliveries/{delivery_id} | Full payload + response body for one delivery |
| POST | /deliveries/{delivery_id}/replay | Re-enqueue a failed/dead delivery |
Subscription lifecycle
/api/dev/webhooks/games/{game_id}{ target_url, subscribed_events }(subscribed_events is ["*"] or a subset of the supported list). Returns 201 on first creation (with the one-time signing_secret) or 200 on update. Updating also re-enables a disabled subscription.{
"success": true,
"created": true,
"signing_secret": "<long-random-string>", // shown ONCE — store it
"signing_secret_note": "Store this signing_secret now — it cannot be retrieved later...",
"subscription": { /* subscription object — see contracts below */ }
}/api/dev/webhooks/games/{game_id}/rotate-secretprevious_secret_expires_at), during which deliveries are signed with both. Returns the new signing_secret once./api/dev/webhooks/games/{game_id}is_active=false. The row and its delivery history are kept for audit; re-create with PUT to re-enable. Returns { success, subscription }./api/dev/webhooks/games/{game_id}/testwebhook.test event to confirm your endpoint verifies signatures and returns 2xx. Returns { success, delivery_id, event_id, note }; the worker fires it within ~10s — watch /deliveries.Observability & replay
/api/dev/webhooks/games/{game_id}/deliveriesstatus(all|pending|in_progress|succeeded|failed|dead), event_type, page (default 1), per_page (default 25, max 100). Returns { success, items: [delivery], pagination: { page, per_page, total, total_pages } }./api/dev/webhooks/deliveries/{delivery_id}payload we sent and the last_response_body your endpoint returned — use it to debug a failure./api/dev/webhooks/deliveries/{delivery_id}/replayfailed or dead delivery. We clone the row with a new event_id but the same idempotency_key, so your dedup still works. Re-enable the subscription first if it’s disabled (409). Capped per logical event (409 with redrive_count once exhausted).{
"success": true,
"replay_delivery_id": 90142,
"replay_event_id": "a1b2c3d4-...",
"original_delivery_id": 90011
}/api/dev/webhooks/games/{game_id}/metrics health_state. Drives the dashboard’s health card.{
"success": true,
"health_state": "healthy", // "never" | "healthy" | "failing"
"metrics": {
"success_rate_24h": 0.987, // 0..1, or null if no data
"success_rate_7d": 0.991,
"deliveries_24h": 1543,
"deliveries_7d": 10872,
"dead_count_24h": 2,
"dead_count_7d": 9,
"p50_ms_24h": 142, // round-trip latency percentiles
"p95_ms_24h": 488,
"recent_failure_count": 0, // last 60 min
"pending_count": 3,
"first_attempt_at": "2026-05-29T...",
"last_attempt_at": "2026-06-05T..."
}
}Response object contracts
Subscription object
| Field | Notes |
|---|---|
| subscription_id / game_id | Identifiers |
| target_url, subscribed_events, is_active | Your current config |
| secret_version | Integer; increments on every rotate-secret. Matches the X-Invo-Secret-Version header. |
| secret_rotated_at, previous_secret_expires_at | Rotation timing; previous_secret_expires_at is null outside a grace window. |
| health_state | "never" | "healthy" | "failing" |
| timeout_seconds_override, max_attempts_override | Retry-policy overrides (null = platform default) |
| max_deliveries_per_minute, compress_payloads | Rate limit + gzip opt-in |
| last_success_at, last_failure_at | Most recent outcomes |
Delivery object
status is one of pending → in_progress → succeeded | failed | dead. Fields: delivery_id, event_id, event_type, status, attempts, last_response_code, last_error, next_retry_at, created_at, last_attempted_at, succeeded_at. The single-delivery endpoint adds payload and last_response_body.
Per-subscription tuning
/api/dev/webhooks/games/{game_id}/retry-policynull to clear an override.| Field | Range | Effect |
|---|---|---|
| timeout_seconds | 1–60 | How long we wait for your 2xx before counting a failure. |
| max_attempts | 1–12 | Retries before a delivery is dead-lettered. |
| max_deliveries_per_minute | 1–600 | Throttle to protect a fragile receiver. A throttled delivery stays pending — it does not consume a retry attempt. |
| compress_payloads | bool | gzip bodies ≥ 1 KB with Content-Encoding: gzip. Only enable if your receiver decodes gzip (it returns 415 otherwise). Verify the signature against the decompressed body. |
30s → 2m → 10m → 1h → 6h → 24h (6 attempts), 10s timeout, no throttle, no compression. The defaults are tuned for most receivers.See also
For the receiver side — signature verification, de-duplication, and the at-least-once contract — see Receiving Webhooks.