Price alerts and webhooks
A price alert watches one printing's Cardmarket trend price (EUR) and POSTs a signed JSON event to your webhook endpoint when its condition is met. Alerts are checked once a day, after Cardmarket's daily price guide is imported (about 07:00 UTC).
1. Register an endpoint
Your endpoint must be a public https URL. The signing secret is returned only when you create the endpoint (or rotate its secret), so store it.
mutation { createWebhookEndpoint(url: "https://example.com/hooks/raritydb") { secret webhookEndpoint { id } } }
raritydb webhooks add https://example.com/hooks/raritydb
raritydb webhooks test <id> # sends a signed "ping" now and shows the response
2. Create alerts
mutation($endpoint: ID!) {
createPriceAlert(cardCode: "sv03.5-006", printingKey: "holo", condition: TREND_ABOVE, threshold: 50, webhookEndpointId: $endpoint) {
id card { name } printing { key }
}
}
raritydb alerts add sv03.5-006 --above 50 --webhook <id>
raritydb alerts add base1-4 --printing <key> --change 15 --webhook <id>
raritydb alerts list
| Condition | Threshold | Fires |
|---|---|---|
TREND_ABOVE |
EUR | The day the trend rises to the threshold or above (not again while it stays there) |
TREND_BELOW |
EUR | The day the trend falls to the threshold or below |
CHANGE_ABOVE_PERCENT |
percent | Every day the trend moves up or down by at least that much from the previous price |
Pass printingKey when the card has more than one printing with a Cardmarket price (see Card.printings). Printings without a Cardmarket product have no prices to watch. Each alert fires at most once a day.
Your plan allows 50 alerts and 3 endpoints (viewer { priceAlertLimit webhookEndpointLimit }).
The event
POST /hooks/raritydb
Content-Type: application/json
X-RarityDB-Event: price_alert.triggered
X-RarityDB-Delivery: 4b0c5c1e-… (unique per delivery; use it to ignore duplicates)
X-RarityDB-Signature: t=1792742400,v1=5f2c…
{
"event": "price_alert.triggered",
"alert": { "id": "…", "condition": "TREND_ABOVE", "threshold": 50.0 },
"card": { "code": "sv03.5-006", "name": "Charizard ex", "number": "006", "set": { "code": "sv03.5", "name": "151" } },
"printing": { "key": "holo", "finish": "holo", "stamps": [], "subtype": null },
"price": { "source": "cardmarket", "currency": "EUR", "asOf": "2026-10-23", "trend": 52.1, "previousTrend": 48.9,
"previousAsOf": "2026-10-22", "changePercent": 6.54 }
}
Answer with any 2xx status within 10 seconds. Anything else is retried 5 times with increasing waits. After 5 failed deliveries in a row, the endpoint shows failingSince until one succeeds. viewer { webhookEndpoints { deliveries { status responseCode error } } } shows the last 20.
Verify the signature
The signature is an HMAC-SHA256 of "{t}.{raw body}" with your endpoint's secret, hex encoded, in the v1 part. Check it against the raw body, and reject old timestamps (5 minutes is a good window):
import { verifyWebhook } from "@raritydb/sdk";
if (!(await verifyWebhook(rawBody, request.headers["x-raritydb-signature"], process.env.RARITYDB_WEBHOOK_SECRET!))) {
return new Response("bad signature", { status: 400 });
}
t, v1 = header.split(",").to_h { |part| part.split("=", 2) }.values_at("t", "v1")
expected = OpenSSL::HMAC.hexdigest("SHA256", secret, "#{t}.#{raw_body}")
valid = ActiveSupport::SecurityUtils.secure_compare(expected, v1.to_s) && (Time.now.to_i - t.to_i).abs < 300
If a secret leaks, rotate it (rotateWebhookSecret, raritydb webhooks rotate <id>); the old one stops working immediately.
From an agent (MCP)
The webhook_endpoints and price_alerts tools take an action (list, create, delete, and for endpoints rotate_secret and test). The general graphql_query tool is read-only.
This page as Markdown: /docs/price-alerts.md