Scrydex
Getting Started

Webhooks

Overview

Webhooks allow your application to receive real-time notifications about events happening in your Scrydex account. Instead of polling the API for updates, Scrydex can push data to your specified URL via HTTP POST requests with a JSON body.

Purpose of Webhooks

Webhooks are ideal for keeping your local database in sync with Scrydex data, triggering automated workflows, or notifying your users about price changes and new card listings.

Supported Webhooks

We currently support the following webhook events:

  • <game>.expansions.prices.raw_updated: Triggered when a card's raw market price is updated.
  • <game>.expansions.prices.graded_updated: Triggered when a card's graded market price is updated.
  • <game>.expansions.pop_reports.updated: Triggered when population report data is updated.

Note: The <game> placeholder should be replaced with the specific TCG slug (e.g., pokemon.expansions.prices.raw_updated). Each TCG-specific documentation section will outline the exact webhook events and payload details available for that game.

Retry Policy

We understand that your services might occasionally be unavailable. If your endpoint does not return a 2xx success status code, Scrydex will retry the delivery.

We retry failed webhooks up to 4 times with exponential backoff before marking the delivery as failed.

Response Timing

Your webhook receiver should be fast to ensure reliable delivery. Ideally, your endpoint should process the event and respond in less than 1 or 2 seconds. Scrydex will time out if a response is not received within 10 seconds.

Here is a sample webhook payload:

{
  "id": "6a0375aea0b689dc8dc57fda",
  "name": "magicthegathering.expansions.prices_updated",
  "data": {
    "expansion_ids": [
      "CNS",
      "PS14",
      "PDP15",
      "PPC1",
      "PM15",
      "CP1",
      "V14",
      "DDN",
      "PKTK",
      "OC14"
    ]
  }
}
Example Request
const express = require('express');
const app = express();

// Use express.raw() to get the raw body for signature verification
app.post('/webhooks', express.raw({ type: 'application/json' }), (req, res) => {
  const signature = req.headers['x-scrydex-signature'];
  const payload = req.body; // This is a Buffer

  // Verify signature...

  console.log('Received webhook:', JSON.parse(payload));

  res.status(200).send('OK');
});

app.listen(3000, () => console.log('Server running on port 3000'));

Security

To ensure that webhook requests are authentic and haven't been tampered with, Scrydex signs every payload. We recommend that you verify these signatures before processing any event.

Scrydex webhooks use HMAC-SHA256 signatures with a shared secret (prefixed with whsec_...) sent via the X-Scrydex-Signature header.

Verifying Signatures

The X-Scrydex-Signature header contains two parts: a Unix timestamp (t) and the HMAC-SHA256 signature (v1).

Format: t=timestamp,v1=signature

1. Construct the Payload

The string to be signed is constructed by concatenating the timestamp, a dot, and the raw request body:
timestamp + "." + raw_request_body

2. The "Raw Body" Rule

Crucial: You must use the raw, unparsed byte string of the request body for signature verification. Using a re-stringified JSON object will fail because changes in spacing, key order, or encoding (Scrydex sends minified UTF-8 JSON) will result in a different signature.

Security Best Practices

  • Replay Protection: Verify that the timestamp t is within a 5-minute window of the current time.
  • Constant-time Comparison: Always use "secure compare" methods to prevent timing attacks.
  • Secret Handling: Treat webhook secrets like API keys. Store them in environment variables and never hardcode them.

Signature Example

X-Scrydex-Signature: t=1625097600,v1=6a2e8593b000676449f993d5626359f518e38812674251a27e792476b76ed6a6

Example Request
const crypto = require('crypto');

function verifySignature(payload, header, secret) {
  const parts = header.split(',');
  const timestamp = parts.find(p => p.startsWith('t=')).split('=')[1];
  const signature = parts.find(p => p.startsWith('v1=')).split('=')[1];

  const signedPayload = `${timestamp}.${payload}`;
  const expectedSignature = crypto
    .createHmac('sha256', secret)
    .update(signedPayload)
    .digest('hex');

  return crypto.timingSafeEqual(
    Buffer.from(signature),
    Buffer.from(expectedSignature)
  );
}