Webhooks
Webhooks
Register an HTTPS URL and Zyron POSTs you signed JSON events as they happen — verifications, signups, match results, dispatch completions, and more.
How webhooks work
You register a URL per guild (up to 5), optionally scoped to specific event types. When something happens, Zyron sends an HTTP POST to that URL with a JSON body and an HMAC signature you can verify — see Verifying signatures. Respond with any 2xx status quickly; do slow work asynchronously.
Delivery format
Every delivery has the same envelope:
| Field | Type | Description |
|---|---|---|
id | string | Unique delivery ID (UUID). Use it to deduplicate retried deliveries. |
event | string | The event type, e.g. member.verified. |
guildId | string | The guild the event belongs to. |
timestamp | string | When the event occurred, ISO 8601. |
data | object | Event-specific payload — see the event catalog. |
{
"id": "3f8a1a2e-9b1c-4c1d-8e2f-7a6b5c4d3e2f",
"event": "member.verified",
"guildId": "123456789012345678",
"timestamp": "2026-07-10T09:15:31.000Z",
"data": {
"discordId": "222222222222222222",
"epicId": "a1b2c3d4e5f60718293a4b5c6d7e8f90",
"epicName": "Ninja",
"platform": "PC",
"verificationMethod": "api"
}
}Each request carries these headers:
| Header | Value |
|---|---|
X-Zyron-Event | The event type, e.g. member.verified. |
X-Zyron-Delivery | The delivery UUID (same as body id). |
X-Zyron-Timestamp | Send time as unix seconds — used in the signature. |
X-Zyron-Signature | sha256=<hex> HMAC over the timestamp and raw body. See Verifying signatures. |
List webhooks
/guilds/:guildId/webhooksReturns your registered webhooks (without secrets), the full list of available event types, and the limits:
{
"webhooks": [
{
"webhookId": "wh_6c92f0b1",
"url": "https://bot.example.com/zyron/webhook",
"events": ["member.verified"],
"enabled": true
}
],
"events": ["member.verified", "member.unlinked", "..."],
"limits": { "maxWebhooks": 5 }
}Create a webhook
/guilds/:guildId/webhooks| Field | Type | Description |
|---|---|---|
urlrequired | string | Your endpoint. Must be https and resolve to a public host — private and internal addresses are rejected. |
events | string[] | Event types to receive. Empty or omitted = all events. |
curl -X POST https://api.zyron.pro/api/v1/guilds/123456789012345678/webhooks \
-H "Authorization: Bearer zyr_your_guild_key" \
-H "Content-Type: application/json" \
-d '{
"url": "https://bot.example.com/zyron/webhook",
"events": ["member.verified", "customs.dispatch.completed"]
}'Returns 201 Created:
{
"webhookId": "wh_6c92f0b1",
"url": "https://bot.example.com/zyron/webhook",
"events": ["member.verified", "customs.dispatch.completed"],
"enabled": true,
"secret": "whsec_8Yt3xk2Lq0PvNwRz5FbHdJ7mAeC1gS9u",
"createdAt": "2026-07-10T09:00:00.000Z"
}The secret is shown once
secret (whsec_...) appears only in this 201 response — it is never returned again. Store it in your secrets manager immediately; if you lose it, delete the webhook and create a new one.HTTPS and public hosts only
https:// and point at a publicly reachable host. URLs resolving to loopback, LAN, or link-local addresses are rejected with 400 invalid_request.Update a webhook
/guilds/:guildId/webhooks/:webhookId| Field | Type | Description |
|---|---|---|
url | string | New endpoint URL (same https/public rules). |
events | string[] | New event filter (empty = all). |
enabled | boolean | Enable or disable delivery. Also used to re-enable a webhook that was auto-disabled. |
Delete a webhook
/guilds/:guildId/webhooks/:webhookIdTest a webhook
/guilds/:guildId/webhooks/:webhookId/testSends a signed zyron.ping event to your endpoint right away and reports what happened:
{
"ok": true,
"status": 200,
"latencyMs": 148
}Retries & auto-disable
Each delivery is attempted up to 3 times — immediately, after 10 seconds, and after 60 seconds — with a 10-second timeout per attempt. Any 2xx response counts as delivered.
Auto-disable after 10 failures
PATCH /webhooks/:webhookId and { "enabled": true }.Event catalog
The data payload per event type:
| Event | data fields |
|---|---|
member.verified | discordId, epicId, epicName, platform, verificationMethod |
member.unlinked | discordId, epicId, epicName, removedBy |
registration.opened | tournamentId, name, lobbyNumber |
registration.closed | tournamentId, name, lobbyNumber |
session.created | tournamentId, name, sessionKey, startDate |
session.cancelled | tournamentId, name, lobbyNumber |
team.registered | tournamentId, teamId, name, captainDiscordId, status |
team.withdrawn | tournamentId, teamId, name, captainDiscordId, status |
team.kicked | tournamentId, teamId, name, captainDiscordId, status |
team.promoted | tournamentId, teamId, name, captainDiscordId, status |
match.results.posted | tournamentId, matchNumber, reportCount, players[] — each with userId, epicName, teamId, teamName, placement, elims, points |
customs.session.created | sessionId, title, gameMode, region, tournamentId, matchNumber |
customs.session.cancelled | sessionId, title, tournamentId |
customs.dispatch.completed | sessionId, title, doneCount, failedCount, cause, tournamentId |
dropmap.locked | tournamentId, name |
modmail.ticket.created | ticketId, number, userId |
modmail.ticket.claimed | ticketId, number, claimedBy |
modmail.ticket.closed | ticketId, number, closedBy, reason |
zyron.ping | Test event sent by POST .../test only. |
tournamentId and matchNumber on customs events are null for standalone (non-tournament) customs sessions.