Skip to main content
Reference

Result webhooks

Instead of polling for the latest draw, register an endpoint and we POST you a signed lottery.result.published event the moment a result lands. PatternSight delivers the published numbers; it does not predict future draws.

1. Subscribe

Add an endpoint from your webhooks dashboard — an https URL, an optional description, and the game_codes you care about (leave blank for every game). The same codes you use elsewhere in the API (/v1/games/{code}). A signing secret (whsec_…) is shown once at creation — store it; you verify every delivery with it.

2. The event

Every delivery is a JSON POST with this shape. Optional fields (country, jackpot) are emitted as null when unknown, never dropped:

{
  "version": "1.0",
  "event": "lottery.result.published",
  "game_code": "powerball",
  "lottery_name": "Powerball",
  "country": "us",
  "draw_date": "2026-06-01",
  "numbers": [7, 14, 21, 35, 49],
  "bonus_numbers": [12],
  "jackpot": 45000000,
  "published_at": "2026-06-01T20:31:05.123456+00:00"
}

3. Verify the signature

Each request carries a Signature header — the hex-encoded HMAC-SHA256 of the exact request body, keyed with your signing secret (the same scheme Stripe and GitHub use). Recompute it over the raw bytesyou received and compare in constant time. Reject anything that doesn't match.

Node

const crypto = require('crypto');

// Verify the raw request body against the Signature header (constant-time).
function verify(rawBody, signatureHeader, secret) {
  const expected = crypto.createHmac('sha256', secret).update(rawBody, 'utf8').digest('hex');
  const a = Buffer.from(expected);
  const b = Buffer.from(signatureHeader || '');
  return a.length === b.length && crypto.timingSafeEqual(a, b);
}

// Express — use express.raw so you hash the EXACT bytes we signed, not a re-serialization.
app.post('/webhooks/patternsight', express.raw({ type: 'application/json' }), (req, res) => {
  const raw = req.body.toString('utf8');
  if (!verify(raw, req.get('Signature'), process.env.PS_WEBHOOK_SECRET)) {
    return res.status(401).end();
  }
  const event = JSON.parse(raw);
  // handle event.game_code, event.draw_date, event.numbers, event.bonus_numbers …
  res.status(200).end();   // 2xx = delivered; non-2xx is retried once
});

Python

import hashlib
import hmac

def verify(raw_body: bytes, signature: str | None, secret: str) -> bool:
    expected = hmac.new(secret.encode(), raw_body, hashlib.sha256).hexdigest()
    return hmac.compare_digest(expected, signature or "")

# FastAPI — read the raw body, never a parsed-then-reserialized one.
@app.post("/webhooks/patternsight")
async def receive(request: Request):
    raw = await request.body()
    if not verify(raw, request.headers.get("Signature"), PS_WEBHOOK_SECRET):
        raise HTTPException(status_code=401)
    event = json.loads(raw)
    # handle event["game_code"], event["numbers"], …
    return Response(status_code=200)

Delivery & retries

  • · Respond 2xx to acknowledge. A non-2xx, a timeout, or a network error is retried once.
  • · Every attempt is logged — status, latency, and attempt count are visible on your webhooks dashboard.
  • ·Send a sample event to a live endpoint any time with the dashboard's Send test button.
  • · Make your handler idempotent — key off game_code + draw_date.