Skip to content
Prepaid, no subscription trap. Top up a balance and we draw the monthly fee from it.
tcggraph

Create a webhook

Register an HTTPS endpoint for signed event deliveries.

POSThttps://api.tcggraph.com/v1/webhooksNo credits · metering

The response carries secret, the key deliveries are signed with. It is returned here and never again; store it. How to verify a signature is on the webhooks page.

Management calls cost no credits and deliveries are free. They still count toward the per-minute rate limit.

Request body

JSON, with Content-Type: application/json. A body that is not valid JSON is a 400 reading “Send a JSON body.”

ParameterTypeDescription
urlrequiredstringPublic https URL. Private, loopback and link-local addresses, localhost, and URLs with credentials in them are refused.
eventsrequiredstring[]At least one event type. Duplicates are dropped.

One of: set.published, card.created, card.updated, card.legality.changed, price.threshold.crossed

gamesstring[]Only events about these games.

One of: pokemon, magic-the-gathering, one-piece, yugioh, disney-lorcana, star-wars-unlimited, digimon, grand-archive, dragon-ball-super

Default: [] (every game)

descriptionstringYour label, up to 200 characters.

Default: null

Response

201 The endpoint, with its signing secret.

FieldTypeDescription
dataWebhookEndpoint (WebhookEndpoint reference)A WebhookEndpoint with secret.
data.secretstringwhsec_ followed by the signing key. Shown once.

Examples

Real responses from the production API, recorded on 2026-09-24 by the script that also checks every field above against them. Arrays are shown in full.

New sets and bans, Pokémon only

curl -X POST "https://api.tcggraph.com/v1/webhooks" \
  -H "Authorization: Bearer $TCGGRAPH_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"url":"https://example.com/tcggraph-docs","events":["set.published","card.legality.changed"],"games":["pokemon"],"description":"docs example"}'
201 · X-Credits-Cost: 0
{
  "data": {
    "id": "c3290649-ffc6-467c-9470-c5307925ca78",
    "url": "https://example.com/tcggraph-docs",
    "events": [
      "set.published",
      "card.legality.changed"
    ],
    "games": [
      "pokemon"
    ],
    "description": "docs example",
    "status": "active",
    "disabledAt": null,
    "disabledReason": null,
    "failures": 0,
    "lastDeliveryAt": null,
    "lastStatusCode": null,
    "createdAt": "2026-09-24T07:47:43.874Z",
    "secret": "whsec_••••••••••••"
  }
}

Errors

Every error has the same body; switch on error.code. Any endpoint can also answer 401, 402, 403 or 429 for reasons of key, plan or rate; those are on Errors.

StatusCodeWhen
400invalid_requestA field is missing or invalid. details.field names it and details.allowed lists the choices where there are some.
409conflictThe URL is already registered on this account.

Unknown event

curl -X POST "https://api.tcggraph.com/v1/webhooks" \
  -H "Authorization: Bearer $TCGGRAPH_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"url":"https://example.com/tcggraph-docs-2","events":["card.reprinted"]}'
400
{
  "error": {
    "code": "invalid_request",
    "message": "Unknown event \"card.reprinted\".",
    "details": {
      "field": "events",
      "allowed": [
        "set.published",
        "card.created",
        "card.updated",
        "card.legality.changed",
        "price.threshold.crossed"
      ]
    }
  }
}

Same URL twice

curl -X POST "https://api.tcggraph.com/v1/webhooks" \
  -H "Authorization: Bearer $TCGGRAPH_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"url":"https://example.com/tcggraph-docs","events":["set.published"]}'
409
{
  "error": {
    "code": "conflict",
    "message": "That URL is already registered on this account.",
    "details": {
      "endpoints": "GET /v1/webhooks lists what is already registered."
    }
  }
}