TANITARDevelopers
Webhooks

Register and manage endpoints

Register a webhook endpoint, test it, enable it, choose its event types and rotate its signing secret.

Webhook endpoints are managed from the Tanitar dashboard by an administrator of your organization (org:admin). The dashboard calls the /dashboard/v1/webhooks routes below with the administrator's session; they are listed here so you know exactly what each screen does. They do not accept an API key. For the screens themselves, see Webhooks in the dashboard.

RouteWhoWhat it does
GET /dashboard/v1/webhooks/event-typesAny memberThe event types you may subscribe to, each with its schemaVersion
GET /dashboard/v1/webhooks/endpointsAny memberYour endpoints
GET /dashboard/v1/webhooks/endpoints/:endpointIdAny memberOne endpoint
POST /dashboard/v1/webhooks/endpointsAdministratorRegister an endpoint; returns its first secret, once
PATCH /dashboard/v1/webhooks/endpoints/:endpointIdAdministratorChange the URL or description
POST /dashboard/v1/webhooks/endpoints/:endpointId/subscriptionsAdministratorReplace the list of event types
POST /dashboard/v1/webhooks/endpoints/:endpointId/testAdministratorSend a signed test delivery
POST /dashboard/v1/webhooks/endpoints/:endpointId/enableAdministratorStart deliveries (requires a passed test)
POST /dashboard/v1/webhooks/endpoints/:endpointId/disableAdministratorStop deliveries
POST /dashboard/v1/webhooks/endpoints/:endpointId/revokeAdministratorRetire the endpoint permanently
GET /dashboard/v1/webhooks/endpoints/:endpointId/secretsAny memberSecret ids and states, never the secret itself
POST /dashboard/v1/webhooks/endpoints/:endpointId/secrets/rotateAdministratorMint a new secret with an overlap window
POST /dashboard/v1/webhooks/endpoints/:endpointId/secrets/:secretId/revokeAdministratorRevoke one secret immediately

Register, test, enable

  1. Register. POST /dashboard/v1/webhooks/endpoints with:

    {
      "url": "https://hooks.example.com/tanitar",
      "description": "Production receiver",
      "eventTypes": ["tournament.tournament.started", "shop.order.settled"]
    }

    url is at most 2048 characters, description at most 200, and eventTypes holds 1 to 100 distinct types from the event catalog. The endpoint starts in state pending with testStatus untested. The response includes a secret object with secretId and the plaintext secret (whsec_…). This is the only time the secret is shown. Store it in your receiver's secret store now.

  2. Deploy your receiver with that secret and the signature check.

  3. Test. POST /dashboard/v1/webhooks/endpoints/:endpointId/test sends one signed delivery of the synthetic type webhook.test, whose data is { "endpointId": "…" }. A 2xx answer sets testStatus to passed; anything else sets failed. The test is not recorded in the delivery log and is not retried.

  4. Enable. POST /dashboard/v1/webhooks/endpoints/:endpointId/enable moves the endpoint to active. It is refused with 409 WEBHOOK_ENDPOINT_UNTESTED unless the last test passed.

From then on, every subscribed event that occurs on your tenant creates a delivery. Events that occurred before the endpoint became active are not delivered to it.

Revisions and If-Match

Every endpoint carries an integer revision. Each mutation, including PATCH, subscriptions, test, enable, disable, revoke, secret rotation and secret revocation, must send the current revision in If-Match (for example If-Match: 3) and increments it. A stale revision is refused with 409 WEBHOOK_REVISION_CONFLICT; read the endpoint again and retry. A missing or non-integer If-Match is a 400.

Subscriptions

POST /dashboard/v1/webhooks/endpoints/:endpointId/subscriptions replaces the whole list:

{ "eventTypes": ["tournament.tournament.started", "tournament.tournament.completed", "reward.grant-applied"] }

Up to 100 distinct types; an empty list keeps the endpoint but stops all deliveries to it. Any type that is not in the event catalog, including webhook.test, refuses the whole request with 400 WEBHOOK_EVENT_TYPE_UNSUPPORTED. A change applies to events that occur after it; deliveries already created are unaffected.

Changing the URL

PATCH /dashboard/v1/webhooks/endpoints/:endpointId accepts url and description, both optional. The URL of an active endpoint cannot change (409 WEBHOOK_ENDPOINT_URL_LOCKED): disable it, change the URL, test again, then enable. A new URL resets testStatus to untested.

URL admission

The URL is checked when you register it, change it, test it, and again before every delivery attempt. It is refused with 400 WEBHOOK_ENDPOINT_UNSAFE when:

  • the scheme is not https;
  • it carries a username or password;
  • the host is localhost, or it is or resolves to a private, loopback, link-local, carrier-grade NAT, benchmarking, multicast or reserved address (IPv4 or IPv6). Every address the name resolves to must be public.

A host name that does not resolve is refused at registration with 400 WEBHOOK_ENDPOINT_UNRESOLVED. Tanitar never follows a redirect from your endpoint, so a 3xx cannot move a delivery to another host.

Signing secrets

Each endpoint has one active secret, and during a rotation also one retiring secret. Every delivery is signed with every secret that is active or retiring and still inside its window, so the webhook-signature header carries one entry per secret.

Rotate a secret

POST /dashboard/v1/webhooks/endpoints/:endpointId/secrets/rotate with an optional body:

{ "overlapSeconds": 86400 }
  1. Tanitar mints a new active secret and returns it once, together with retiringSecretId and retiringUntil.
  2. The previous secret becomes retiring until retiringUntil. overlapSeconds defaults to 24 hours (86400) and is capped at that value; 0 revokes the previous secret immediately.
  3. During the window every delivery carries two signatures. Install the new secret in your receiver; a receiver holding either secret verifies successfully.
  4. When the window closes, the retiring secret is revoked and its material deleted.

A secret that was already retiring when you rotate again is revoked on the spot: an endpoint never holds more than one active and one retiring secret.

Revoke a secret

POST /dashboard/v1/webhooks/endpoints/:endpointId/secrets/:secretId/revoke revokes one secret immediately, for example a retiring secret you no longer need, or a key you believe leaked. If you revoke the active secret and no other remains, test deliveries are refused with 409 WEBHOOK_SIGNING_SECRET_MISSING and pending deliveries settle as dead with SIGNING_SECRET_MISSING; rotate to mint a new secret.

GET /dashboard/v1/webhooks/endpoints/:endpointId/secrets lists secretId, state (active, retiring, revoked), retiringUntil and createdAt. It never returns key material.

Disable and revoke

  • POST /dashboard/v1/webhooks/endpoints/:endpointId/disable stops deliveries; you can enable the endpoint again later.
  • POST /dashboard/v1/webhooks/endpoints/:endpointId/revoke retires the endpoint permanently and revokes all its secrets. A revoked endpoint accepts no further change (409 WEBHOOK_ENDPOINT_REVOKED) and no longer counts toward the limit of 10.

What each state does to deliveries in flight is described in Endpoint states.

Error codes

Refusals use the shared error envelope with these errorCode values:

errorCodeStatusMeaning
WEBHOOK_ENDPOINT_UNSAFE400The URL fails admission
WEBHOOK_ENDPOINT_UNRESOLVED400The host name does not resolve
WEBHOOK_EVENT_TYPE_UNSUPPORTED400An event type is not exposed to webhooks
WEBHOOK_ENDPOINT_LIMIT409Your organization already has 10 non-revoked endpoints
WEBHOOK_REVISION_CONFLICT409If-Match does not match the current revision
WEBHOOK_ENDPOINT_UNTESTED409Enable attempted without a passed test
WEBHOOK_ENDPOINT_URL_LOCKED409URL change attempted on an active endpoint
WEBHOOK_ENDPOINT_REVOKED409The endpoint is revoked
WEBHOOK_SIGNING_SECRET_MISSING409Test attempted with no usable secret

On this page