Bet on YesPlay Bet on Hollywoodbets
Bet on YesPlay Bet on Hollywoodbets
ResultsZA Webhook Push: real-time lottery results delivered to your server, application, system or platform

Webhook Payload Reference

Every delivery is a signed HTTP POST with a JSON body. Verify authenticity by computing HMAC-SHA256(raw_request_body, webhook_secret) and comparing to the X-ResultsZA-Signature header. Click any game pill to see its payload.

Official SDKs

Verify webhooks without the crypto

Our SDKs include HMAC-SHA256 signature verification and event parsing, so you can consume ResultsZA webhooks securely without writing the crypto yourself.

Python pip install resultsza-sdk
Node npm install resultsza-sdk

Quick facts

  • Method: HTTPS POST to your endpoint (plain HTTP is rejected).
  • Content-Type: application/json — the body is a single JSON object.
  • Authentication: HMAC-SHA256 signature in X-ResultsZA-Signature (verify against the raw body). Authenticate with the signature, not the source IP. If you need a fixed source IP for a firewall allowlist, contact us.
  • Acknowledge: return any 2xx (200, 201, 202 or 204 are all treated as success) within 10 seconds. The response body can be empty and needs no particular Content-Type.
  • On failure: a non-2xx response, a timeout, or an unreachable endpoint is retried up to 3 times, 10 minutes apart, then marked failed (you get an email and a Delivery Log entry).
  • Delivery: at-least-once and not strictly ordered — de-duplicate per draw (see Building your listener for the per-game key).
  • Payload size: most deliveries are a few KB; horse racing is the largest (a full race day across venues can reach tens of KB). There is no fixed cap, so don't set your request-body limit too low.
  • Stability: the payload is unversioned and changes are additive only. New fields may be added over time, so ignore unknown fields rather than failing on them.

Request Headers

Content-Type: application/json
X-ResultsZA-Signature: sha256=<hmac_hex>
X-ResultsZA-Game: lotto
X-ResultsZA-Event: result.published

Test payloads sent from the settings page use "event": "result.test".

Envelope (all games)

{
  "event": "result.published",
  "game": "<game_id>",
  "country": "<ISO country code>",
  "dispatched_at": "2026-06-25T21:05:00Z",
  "data": { ... }
}

The data object varies by game, see each section below.

FieldTypeNotes
eventstringresult.published for real results. Test sends from Push Settings use result.test — handle or explicitly ignore it so test traffic does not enter your live pipeline.
gamestringLowercase game id, also sent in the X-ResultsZA-Game header (e.g. lotto, us powerball, horse_racing).
countrystringISO-style code: ZA, NG, GH, KE, GB, EU, US.
dispatched_atstringUTC timestamp in ISO 8601, e.g. 2026-06-25T21:05:00Z (the trailing Z denotes UTC).
dataobjectGame-specific result fields, see each section below.

Building your listener

Your endpoint needs to do three things: accept an HTTPS POST, verify the signature against the raw request body, and return a 2xx response within 10 seconds.

Endpoint requirements

  • Reachable over HTTPS (plain HTTP is rejected).
  • Returns a 2xx status within 10 seconds, otherwise the delivery is treated as failed and retried.
  • Do slow work (database writes, downstream calls) after you respond, not before.

Verify the signature

Verify against the raw request bytes, exactly as received. If your framework parses the JSON and you then re-serialise it, the bytes change and the signature will never match. The header value includes the sha256= prefix, so compare against the full string.

Python (Flask):

import hmac, hashlib
from flask import Flask, request, abort

app = Flask(__name__)
SECRET = b"your_webhook_secret"          # from Push Settings

@app.post("/webhooks/resultsza")
def resultsza():
    body = request.get_data()            # raw bytes, before any parsing
    expected = "sha256=" + hmac.new(SECRET, body, digestmod=hashlib.sha256).hexdigest()
    received = request.headers.get("X-ResultsZA-Signature", "")
    if not hmac.compare_digest(expected, received):
        abort(403)
    event = request.get_json()
    # de-dupe on event["game"] + event["data"].get("draw_date") before processing
    return "", 200

Node (Express):

const express = require("express");
const crypto = require("crypto");
const app = express();
const SECRET = "your_webhook_secret";    // from Push Settings

// express.raw keeps the body as bytes so the signature verifies byte-for-byte
app.post("/webhooks/resultsza",
  express.raw({ type: "application/json" }),
  (req, res) => {
    const expected = "sha256=" +
      crypto.createHmac("sha256", SECRET).update(req.body).digest("hex");
    const received = req.get("X-ResultsZA-Signature") || "";
    if (expected.length !== received.length ||
        !crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(received))) {
      return res.sendStatus(403);
    }
    const event = JSON.parse(req.body.toString("utf8"));
    // de-dupe on event.game + event.data.draw_date before processing
    res.sendStatus(200);
  });

Delivery is at-least-once

Treat every delivery as at-least-once and not strictly ordered. The same result can arrive more than once (for example, if your endpoint is slow and a retry overlaps a late success), so make processing idempotent. De-duplicate per game using a key that is unique per draw:

  • SA Lottery: game + data.draw_number (or data.draw_date).
  • Nigeria (Baba Ijebu) and Ghana: game + data.draw_date + data.draw_time — these run several draws per day, so the date alone is not unique.
  • Kenya, UK 49s, EuroMillions, Irish Lotto, US: game + data.draw_date (each draws at most once per day).
  • Horse racing: data.race_date — a full day across all venues is sent as a single horse_racing payload, so there is only ever one delivery per date.

Retries

A non-2xx response or a timeout is retried up to 3 times, 10 minutes apart. After the final failure the delivery is marked failed, you receive an email, and it appears in the Delivery Log on your Push Settings page.

Testing your endpoint

From Push Settings in your portal, click Test Webhook to send a live, signed sample to your URL (it carries "event": "result.test") so you can confirm reachability and signature verification before any real results arrive. Returning a 2xx to that test is the quickest way to know your listener is wired up correctly.

To see exactly what a payload looks like before you write any code, use a free request-inspection tool like webhook.site (or RequestBin / Beeceptor). Grab the temporary URL it gives you, paste it as your webhook URL in Push Settings, then hit Test Webhook (or wait for a real draw). The tool captures the full request, so you can inspect the exact JSON body, the headers, and the X-ResultsZA-Signature value as they actually arrive. Remember to switch your webhook URL back to your real endpoint when you're done.

Monitoring deliveries

Every dispatch is recorded in the Delivery Log in your portal (Push Settings › Delivery Log). It lists your recent deliveries (paginated, retained for 15 days) with the game, draw date, status (delivered / failed / pending), attempt count, last attempted time and delivered at time, so you can confirm exactly what we sent and diagnose any failures on your side without contacting support.

Game Format Reference

Game game field Country Numbers Range Bonus / Special Format
Daily LottodailylottoZA51-36None (bonus_ball: null)string
LottolottoZA61-52Bonus ball, range 1-52string
Lotto Plus 1lotto plus1ZA61-52Bonus ball, range 1-52string
Lotto 5 Maxlotto 5 maxZA61-52Bonus ball, range 1-52string
PowerballpowerballZA51-50powerball, range 1-16string
Powerball Xtrapowerball xtraZA51-50powerball, range 1-16string
Nigeria
Baba Ijebu (25 draws)premier <name>NG51-90Noneinteger array
Ghana
Ghana Lottery (10 draws)ghana <name>GH51-90Noneinteger array
Kenya
Kenya Lotto (daily)Kenya Lotto <Day>KE60-39Nonestring
kenya mega jackpotkenya mega jackpotKE60-39Nonestring
UK
UK 49s (4 draws/day)uk49s <draw>GB61-49Booster ball (booster_ball), range 1-49string
Europe
euromillionseuromillionsEU51-502 Lucky Stars (lucky_star_1/2), range 1-12string
Irish Lottoirish lottoIE61-47Bonus ball (bonus_ball), range 1-47string
USA
US Powerballus powerballUS51-69Powerball (special_ball), range 1-26string
US Mega Millionsus mega millionsUS51-70Mega Ball (special_ball), range 1-24string
Horse Racing
Horse Racinghorse_racingZAStructured meeting/race/finisher/dividend data for the full racing day-

Format: string = comma-separated e.g. "4, 11, 23"integer array = JSON array e.g. [3, 17, 28]

Game Payloads

SA Lottery

dailylotto lotto lotto plus1 lotto 5 max powerball powerball xtra
{
  "event": "result.published",
  "game": "dailylotto",
  "country": "ZA",
  "dispatched_at": "2026-06-25T18:48:26Z",
  "data": {
    "draw_number": "2657",
    "draw_date": "2026-06-24",
    "winning_numbers": "05, 07, 19, 24, 35",
    "bonus_ball": null,
    "jackpot": 380000,
    "divisions": [
      { "division": "DIV 1", "match": "MATCH 5", "winners": 2,     "prize": 177684.8 },
      { "division": "DIV 2", "match": "MATCH 4", "winners": 327,   "prize": 310.5 },
      { "division": "DIV 3", "match": "MATCH 3", "winners": 9046,  "prize": 16.8 },
      { "division": "DIV 4", "match": "MATCH 2", "winners": 85994, "prize": 4.7 }
    ]
  }
}
FieldTypeNotes
draw_numberstringOfficial draw number. Always a numeric string (e.g. "2657"), not a JSON integer, so parse it before any numeric comparison.
draw_datestringYYYY-MM-DD
winning_numbersstring Comma-separated, zero-padded to two digits, e.g. "05, 07, 19, 24, 35".
dailylotto: 5 balls, range 1-36
lotto / lotto plus1 / lotto 5 max: 6 balls, range 1-52
powerball / powerball xtra: 5 balls, range 1-50
bonus_ballinteger or null dailylotto: always null (no bonus ball)
lotto / lotto plus1 / lotto 5 max: bonus ball, range 1-52
Not present for powerball / powerball xtra — use powerball instead
powerballintegerpowerball / powerball xtra only. Range 1-16
jackpotnumber or nullNext jackpot estimate in ZAR (integer); null if not yet known
divisionsarray or nullPrize breakdown; null if not yet published. Each item: division (string, e.g. "DIV 1"; prefixed per game, e.g. "Plus1 DIV 1", "5 MAX DIV 1", "XTRA DIV 1"), match (string, e.g. "MATCH 5", "MATCH 5 + BONUS", "MATCH PowerBall"), winners (integer), prize (number, ZAR)

Nigeria (Baba Ijebu)

premier 06 premier aseda premier bingo premier bonanza premier club master premier diamond premier enugu premier fairchance premier fortune premier gold premier international premier jackpot premier king premier lucky premier lucky g premier mark ii premier metro premier midweek premier msp premier national premier peoples premier royal premier super premier tota premier vag
{
  "event": "result.published",
  "game": "premier 06",
  "country": "NG",
  "dispatched_at": "2026-06-25T18:50:04Z",
  "data": {
    "draw_date": "2026-06-23",
    "draw_time": "12:45",
    "numbers": [35, 85, 88, 8, 57]
  }
}
FieldTypeNotes
draw_datestringYYYY-MM-DD
draw_timestring or nullHH:MM (WAT, Nigeria local time)
numbersarray of integers5 numbers, range 1-90 (order as drawn, not sorted)

Ghana Lottery

ghana monday special ghana lucky tuesday ghana mid-week ghana fortune thursday ghana friday bonanza ghana national weekly ghana sunday aseda ghana vag lotto ghana noon rush ghana daywa 5/39
{
  "event": "result.published",
  "game": "ghana monday special",
  "country": "GH",
  "dispatched_at": "2026-06-25T18:53:14Z",
  "data": {
    "draw_date": "2026-06-23",
    "draw_time": "20:00",
    "numbers": [43, 32, 45, 77, 11]
  }
}
FieldTypeNotes
draw_datestringYYYY-MM-DD
draw_timestring or nullHH:MM (GMT; Ghana is UTC+0)
numbersarray of integers5 numbers, range 1-90 (order as drawn, not sorted)

Kenya Lotto

kenya lotto monday kenya lotto tuesday kenya lotto thursday kenya lotto friday kenya lotto sunday kenya mega jackpot
{
  "event": "result.published",
  "game": "kenya lotto monday",
  "country": "KE",
  "dispatched_at": "2026-06-25T18:53:14Z",
  "data": {
    "draw_date": "2026-06-22",
    "draw_time": "23:00",
    "winning_numbers": "12, 21, 22, 29, 36, 37"
  }
}
FieldTypeNotes
draw_datestringYYYY-MM-DD
draw_timestring or nullHH:MM (EAT, Kenya local time)
winning_numbersstring6 numbers, range 0-39, comma-separated (not zero-padded)

UK 49s

uk49s brunchtime uk49s lunchtime uk49s drivetime uk49s teatime
{
  "event": "result.published",
  "game": "uk49s brunchtime",
  "country": "GB",
  "dispatched_at": "2026-06-25T15:05:00Z",
  "data": {
    "draw_date": "2026-06-25",
    "winning_numbers": "7, 10, 11, 17, 26, 35",
    "booster_ball": 49
  }
}
FieldTypeNotes
draw_datestringYYYY-MM-DD
winning_numbersstring6 numbers, range 1-49, comma-separated
booster_ballintegerRange 1-49

euromillions

euromillions
{
  "event": "result.published",
  "game": "euromillions",
  "country": "EU",
  "dispatched_at": "2026-06-25T18:53:16Z",
  "data": {
    "draw_date": "2026-06-23",
    "winning_numbers": "3, 33, 36, 45, 46",
    "lucky_star_1": 5,
    "lucky_star_2": 6,
    "jackpot": 47884519.6
  }
}
FieldTypeNotes
draw_datestringYYYY-MM-DD
winning_numbersstring5 numbers, range 1-50, comma-separated (not zero-padded)
lucky_star_1integerFirst Lucky Star, range 1-12
lucky_star_2integerSecond Lucky Star, range 1-12
jackpotnumber or nullJackpot in EUR; may be a decimal (e.g. 47884519.6); null if not yet published

Draws every Tuesday and Friday at ~21:00 CET (23:00 SAST in summer).

irish lotto

irish lotto
{
  "event": "result.published",
  "game": "irish lotto",
  "country": "IE",
  "dispatched_at": "2026-06-27T19:05:11Z",
  "data": {
    "draw_date": "2026-06-27",
    "winning_numbers": "12, 27, 29, 38, 43, 46",
    "bonus_ball": 25,
    "jackpot": 5181046.0
  }
}
FieldTypeNotes
draw_datestringYYYY-MM-DD
winning_numbersstring6 numbers, range 1-47, comma-separated (not zero-padded)
bonus_ballinteger or nullBonus ball, range 1-47 (auto-drawn from the same pool)
jackpotnumber or nullJackpot in EUR; may be a decimal; null if not yet published

Draws every Wednesday and Saturday at ~18:45 UTC (20:45 SAST).

US Lottery

us powerball us mega millions
{
  "event": "result.published",
  "game": "us powerball",
  "country": "US",
  "dispatched_at": "2026-06-25T18:58:32Z",
  "data": {
    "draw_date": "2026-06-24",
    "winning_numbers": "13, 14, 16, 21, 38",
    "special_ball": 14,
    "multiplier": 2
  }
}
FieldTypeNotes
draw_datestringYYYY-MM-DD (ET draw date)
winning_numbersstring5 numbers (us powerball: range 1-69; us mega millions: range 1-70), comma-separated
special_ballintegerus powerball: range 1-26. us mega millions (Mega Ball): range 1-24
multiplierinteger or nullPower Play / Megaplier; null if not available

us powerball: Mon / Wed / Sat. us mega millions: Tue / Fri. Results arrive ~05:00 SAST the morning after the draw.

Horse Racing

horse_racing
{
  "event": "result.published",
  "game": "horse_racing",
  "country": "ZA",
  "dispatched_at": "2026-06-25T19:14:32Z",
  "data": {
    "race_date": "2026-06-25",
    "meetings": [
      {
        "venue": "Vaal",
        "track_code": "V",
        "province": "G",
        "races": [
          {
            "race_no": 1,
            "race_name": "Maiden Juvenile Plate",
            "race_class": "MJP",
            "distance": 1200,
            "going": "g",
            "off_time": "12:00:00",
            "win_time": 69.26,
            "stake_total": 125000,
            "finishers": [
              {
                "finish": 1,
                "horse_name": "Summerfest",
                "jockey": "C Zackey",
                "trainer": "SG Tarry",
                "tote_win": 6.9,
                "tote_place": 1.8,
                "sp": "8/1"
              },
              {
                "finish": 2,
                "horse_name": "Molten Rock",
                "jockey": "G Lerena",
                "trainer": "JAJ v Vuuren",
                "tote_win": null,
                "tote_place": 1,
                "sp": "87/100"
              },
              {
                "finish": 3,
                "horse_name": "Vision Of Gold",
                "jockey": "C Murray",
                "trainer": "Mike / Mathew de Kock",
                "tote_win": null,
                "tote_place": 1.9,
                "sp": "11/2"
              }
            ],
            "dividends": [
              { "bet_type": "Exacta",   "selections": "10/8",      "dividend": 13.9 },
              { "bet_type": "Swinger",  "selections": "10/11",     "dividend": 4.2 },
              { "bet_type": "Trifecta", "selections": "10/8/11",   "dividend": 65.6 },
              { "bet_type": "Quartet",  "selections": "10/8/11/4", "dividend": 801 }
            ]
          }
        ]
      }
    ],
    "races_scraped": 9,
    "dividends_found": 74
  }
}
FieldTypeNotes
race_datestringYYYY-MM-DD — the racing day
races_scrapedintegerTotal races stored for this date across all meetings
dividends_foundintegerTote dividend rows stored across the day (Swinger, Exacta, Trifecta, Quartet, Double, Pick 3, Pick 6, Jackpot, Bipot, Place Accumulator)
meetingsarrayOne object per race venue on the day
meetings[].venuestringRacecourse name, e.g. "Vaal", "Greyville"
meetings[].track_codestringShort venue code, e.g. "V" (Vaal), "D" (Greyville)
meetings[].provincestringProvince code, e.g. "G", "N"
meetings[].racesarrayRaces at this venue, ordered by race number
races[].race_nointegerRace number on the card
races[].race_namestringOfficial race name / conditions
races[].race_classstringRace classification as published, e.g. "MJP", "MR66", "Cnd", "MR110"
races[].distanceintegerRace distance in metres
races[].goingstring or nullTrack condition code as published, e.g. "g" (often null)
races[].off_timestringScheduled start time HH:MM:SS
races[].win_timenumber or nullWinning time in seconds, e.g. 69.26 (null if not published)
races[].stake_totalintegerTotal prize money in ZAR (null if not published)
races[].finishersarrayTop 3 finishers (1st, 2nd, 3rd)
finishers[].finishintegerFinishing position (1, 2, or 3)
finishers[].horse_namestringHorse name
finishers[].jockeystringJockey name (null if not available)
finishers[].trainerstringTrainer name (null if not available)
finishers[].tote_winfloatTote win dividend — only set for finish=1 (null otherwise)
finishers[].tote_placefloatTote place dividend (null if not available)
finishers[].spstringStarting price, e.g. "7/1" (null if not available)
races[].dividendsarrayPool dividends for this race
dividends[].bet_typestringTote pool name, e.g. "Swinger", "Exacta", "Trifecta", "Quartet", "Double", "Pick 3", "Pick 6", "Jackpot", "Bipot", "Place Accumulator". Win/place payouts are on each finisher's tote_win/tote_place, not here.
dividends[].selectionsstringWinning horse numbers, slash-separated per position, e.g. "10/8" (Exacta) or "10/8/11/4" (Quartet); multi-leg pools use commas within a leg, e.g. "2,4/2/3"
dividends[].dividendnumberPayout per R1 unit staked

Questions? Contact info@resultsza.co.za