Webhooks
Signed, retried, ordered per endpoint. Subscribe to the events that matter instead of polling the catalog.
Events
| Event | Fires when |
|---|---|
| set.published | A new set is indexed and its cards are queryable. |
| card.created | A card appears, including spoilers ahead of a set release. |
| card.updated | Errata, Oracle changes or a corrected collector number. |
| card.legality.changed | A ban list or rotation moved a card between formats. |
| price.threshold.crossed | A card you watch crossed a price you configured. |
| export.completed | A bulk export finished and the download URL is ready. |
Registering an endpoint
Scope a subscription to specific events and games so you are not woken up by traffic you do not care about.
curl -X POST "https://api.tcggraph.com/v1/webhooks" \
-H "Authorization: Bearer $TCGGRAPH_KEY" \
-H "Content-Type: application/json" \
-d '{
"url": "https://example.com/hooks/tcggraph",
"events": ["set.published", "price.threshold.crossed"],
"games": ["pokemon", "one-piece"]
}'Payload
{
"id": "evt_01JQ8Z3M2K",
"type": "card.legality.changed",
"createdAt": "2026-08-30T18:04:11Z",
"data": {
"cardId": "ygo_89631139",
"game": "yugioh",
"format": "advanced",
"from": "limited",
"to": "forbidden"
}
}Verifying signatures
Every delivery carries a TCGGraph-Signature header of the form t=<timestamp>,v1=<hex>, an HMAC-SHA256 over timestamp.body. Always verify against the raw body, before JSON parsing.
import { createHmac, timingSafeEqual } from "node:crypto";
export function verify(rawBody: string, header: string, secret: string) {
const [timestamp, signature] = header.split(",").map((p) => p.split("=")[1]);
// Reject anything older than five minutes to blunt replay attacks.
if (Math.abs(Date.now() / 1000 - Number(timestamp)) > 300) return false;
const expected = createHmac("sha256", secret)
.update(`${timestamp}.${rawBody}`)
.digest("hex");
return timingSafeEqual(Buffer.from(expected), Buffer.from(signature));
}Delivery guarantees
- At-least-once delivery. Deduplicate on the event
id. - Retries at 1s, 10s, 1m, 10m, 1h and 6h. A
2xxwithin ten seconds counts as success. - Endpoints failing for 24 hours are disabled and you are emailed.
- Deliveries to one endpoint are ordered; across endpoints they are independent.