Replacing a results poller
Subscribe to result.received for your region. A process polling every thirty seconds is about ninety thousand requests a month; this is none.
Documentation / Webhooks & account
/v1/webhooks/subscribe
Point a URL at an event and we sign every delivery.
Send your key in the X-API-Key header, or as
?key= if your tool cannot set one. A date outside your plan's window returns
403 outside_window naming the window, never an empty array.
| Name | In | Type | Required | Description |
|---|---|---|---|---|
| url | body | string | yes | Where to deliver. Must be https. We will not post to a plain http endpoint, because the payload identifies your account. |
| events | body | array | yes | One or more of result.received, race.abandoned, meeting.abandoned, racecard.published. |
| region | body | string | no | Only deliver for one jurisdiction. Most consumers want this; without it you receive twelve countries. |
| course_id | body | string | no | Only deliver for one course. |
| secret | body | string | no | Your own signing secret. If you do not supply one we generate it and return it once, in this response only. |
curl https://api.apihorseracing.com/v1/webhooks/subscribe \ -H "X-API-Key: $AHR_KEY"
$ch = curl_init('https://api.apihorseracing.com/v1/webhooks/subscribe'); curl_setopt_array($ch, [ CURLOPT_RETURNTRANSFER => true, CURLOPT_HTTPHEADER => ['X-API-Key: ' . getenv('AHR_KEY')], ]); $data = json_decode(curl_exec($ch), true)['data'];
const res = await fetch( 'https://api.apihorseracing.com/v1/webhooks/subscribe', { headers: { 'X-API-Key': process.env.AHR_KEY } } ); const { meta, data } = await res.json();
import os, requests r = requests.get( "https://api.apihorseracing.com/v1/webhooks/subscribe", headers={"X-API-Key": os.environ["AHR_KEY"]}, ).json()
curl https://api.apihorseracing.com/v1/webhooks/subscribe?key=$AHR_KEY
$url = 'https://api.apihorseracing.com/v1/webhooks/subscribe' . '?key=$AHR_KEY'; $data = json_decode(file_get_contents($url), true)['data'];
const res = await fetch( 'https://api.apihorseracing.com/v1/webhooks/subscribe?key=$AHR_KEY' ); const { meta, data } = await res.json();
import os, requests r = requests.get( "https://api.apihorseracing.com/v1/webhooks/subscribe?key=$AHR_KEY", ).json()
No sample has been captured for this endpoint yet. Use the console below against your own key.
| Code | Status | When |
|---|---|---|
| unauthorized | 401 | No key, or one we do not recognise. Check the header name and that the key has not been rotated from your account page. |
| forbidden | 403 | The account is disabled or the key revoked. Contact support; this is never a rate limit. |
| invalid_param | 422 | A date that is not YYYY-MM-DD, a by= dimension we do not have, or a cursor that did not come from us. The message names the parameter. |
| rate_limited | 429 | Past your per-minute rate. Retry-After is set, and X-RateLimit-Remaining tells you where you stand on every successful response too. |
| server_error | 500 | Ours, not yours. Every response carries a request_id; quote it and we can find the exact request. |
A real request from your browser straight to
api.apihorseracing.com with
your own key. Nothing is proxied and nothing is logged by this page.
Remembered in this browser so you do not paste it on every page. Forget it
Point a URL at an event and we tell you when it happens, instead of you asking every thirty seconds whether it has.
Four events. result.received when a race settles, which is the one nearly everybody wants. race.abandoned and meeting.abandoned when racing is called off, which is the event that silently breaks schedulers. And racecard.published when declarations land for a future day.
Deliveries do not count against your quota. Polling for results is the single largest source of wasted requests we see, and charging you for the alternative would be a strange way to discourage it.
Every delivery carries an X-AHR-Signature header: an HMAC-SHA256 of the raw body using your secret. Verify it before trusting the payload, and compare with a constant-time function rather than a string equality. The secret is returned once at subscription and never again; rotate by subscribing afresh.
A delivery that does not return a 2xx is retried with an increasing gap for about a day, then given up on. Deliveries are at-least-once, not exactly-once, so your handler needs to tolerate seeing the same race twice. Key on the race identifier and it will.
Subscribe to result.received for your region. A process polling every thirty seconds is about ninety thousand requests a month; this is none.
race.abandoned is the event that saves a scheduler from waiting forever on a result that will never arrive.
racecard.published tells you tomorrow is ready, rather than you guessing at what time to look.
The subscription belongs to the account rather than to the key, so rotating your key does not stop deliveries.