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

MethodPathPurpose
GET/supported-eventsList 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-secretRotate the signing secret (7-day grace)
POST/games/{game_id}/testFire a synthetic webhook.test event
GET/games/{game_id}/metricsDelivery metrics + health state
PATCH/games/{game_id}/retry-policyPer-subscription tuning overrides
GET/games/{game_id}/deliveriesPaginated delivery history
GET/deliveries/{delivery_id}Full payload + response body for one delivery
POST/deliveries/{delivery_id}/replayRe-enqueue a failed/dead delivery

Subscription lifecycle

PUT/api/dev/webhooks/games/{game_id}
Create or update. Body: { 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.
PUT response (first creation)
{
  "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 */ }
}
POST/api/dev/webhooks/games/{game_id}/rotate-secret
Mint a new signing secret. The old one stays valid for a 7-day grace window (until previous_secret_expires_at), during which deliveries are signed with both. Returns the new signing_secret once.
DELETE/api/dev/webhooks/games/{game_id}
Soft-delete — sets is_active=false. The row and its delivery history are kept for audit; re-create with PUT to re-enable. Returns { success, subscription }.
POST/api/dev/webhooks/games/{game_id}/test
Enqueue a synthetic webhook.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

GET/api/dev/webhooks/games/{game_id}/deliveries
Paginated history. Query params: status(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 } }.
GET/api/dev/webhooks/deliveries/{delivery_id}
The full record for one delivery, including the payload we sent and the last_response_body your endpoint returned — use it to debug a failure.
POST/api/dev/webhooks/deliveries/{delivery_id}/replay
Re-enqueue a failed 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).
Replay response
{
  "success": true,
  "replay_delivery_id": 90142,
  "replay_event_id": "a1b2c3d4-...",
  "original_delivery_id": 90011
}
GET/api/dev/webhooks/games/{game_id}/metrics
Delivery metrics over the last 24h / 7d plus the rolled-up health_state. Drives the dashboard’s health card.
Metrics response
{
  "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

FieldNotes
subscription_id / game_idIdentifiers
target_url, subscribed_events, is_activeYour current config
secret_versionInteger; increments on every rotate-secret. Matches the X-Invo-Secret-Version header.
secret_rotated_at, previous_secret_expires_atRotation timing; previous_secret_expires_at is null outside a grace window.
health_state"never" | "healthy" | "failing"
timeout_seconds_override, max_attempts_overrideRetry-policy overrides (null = platform default)
max_deliveries_per_minute, compress_payloadsRate limit + gzip opt-in
last_success_at, last_failure_atMost recent outcomes

Delivery object

status is one of pendingin_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

PATCH/api/dev/webhooks/games/{game_id}/retry-policy
Override platform defaults for just your subscription. Send any subset; pass null to clear an override.
FieldRangeEffect
timeout_seconds1–60How long we wait for your 2xx before counting a failure.
max_attempts1–12Retries before a delivery is dead-lettered.
max_deliveries_per_minute1–600Throttle to protect a fragile receiver. A throttled delivery stays pending — it does not consume a retry attempt.
compress_payloadsboolgzip 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.
Leave these unset to use the platform defaults — backoff 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.