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.