Guide to Live Tennis API Webhooks: Features and Best Practices
Live Tennis API webhooks push live tennis data to your servers as it changes: every accepted score update, every played point, and every break point as it arises and resolves. If you build scoreboards, betting tools or broadcast graphics on Live Tennis API, webhooks are an alternative to holding a WebSocket open or polling the scores endpoints.
This guide covers how Live Tennis API webhooks are registered and signed, the frames they carry, the delivery rules that make them unusual, and the practices that keep a receiver from missing points.
What are Live Tennis API webhooks?
A Live Tennis API webhook is an HTTPS URL registered through the API. It receives the same frames as the push feed, one POST per frame. Webhooks are an ULTRA plan feature and need a direct API key: marketplace keys can't register them.
Each delivery has three headers. X-LTAPI-Signature holds sha256= and the hex HMAC-SHA256 of the raw body, keyed with the webhook's secret. X-LTAPI-Event names the frame type. X-LTAPI-Timestamp is the send time in Unix seconds, and it is not part of the signature.
What sets these webhooks apart is the delivery model. Live Tennis API makes one attempt per frame, with a timeout of about three seconds, and never retries or replays. Score frames recover on their own because each one is the full current score. Point frames don't, so a receiver needs a catch-up path.
Live Tennis API webhook features
| Feature | Details |
|---|---|
| Configuration | API only: POST, GET and DELETE on /api/public/v1/webhooks |
| Requirements | ULTRA plan and a direct (not marketplace) API key |
| Limit | 3 webhooks per key |
| Frame type | The X-LTAPI-Event header |
| Signature header | X-LTAPI-Signature: sha256=<hex> |
| Scheme | HMAC-SHA256, hex digest |
| Signed content | The raw request body only |
| Secret | One per webhook, shown only in the 201 registration response |
| Timestamp | X-LTAPI-Timestamp, Unix seconds. Not signed |
| Delivery | At-most-once: one attempt per frame, no retries, no replay |
| Timeout | About 3 seconds. Redirects are not followed |
| Auto-disable | After 25 consecutive failures |
| Source IPs | Not published |
Common events
X-LTAPI-Event carries one of four frame types:
| Frame | Sent when | Missed delivery |
|---|---|---|
score | A live score commit is accepted. The frame is the complete current score | Self-corrects on the next commit |
point | A point is played. The frame carries one point, with a per-match seq | Lost unless you fetch it again |
break_point | A break point arises, with server, returner and break_points | Lost |
break_point_result | A break point resolves, with outcome of held or broken | Lost |
The registration body's events array decides what a webhook receives. It defaults to ["score"]. The API spec lists score, break_point, point, status and stoppages as accepted values, where break_point covers both break point frames and point is sent only where the point surface is enabled and the match has pbp_coverage: "point". The webhook documentation describes only the four frame types above, so log the X-LTAPI-Event value of anything else before relying on it.
A point frame carries type, match_id, published_at, pbp_coverage, quality and a point object. Inside it, seq is per-match, monotonic and never skips a value, which makes it the deduplication and resume key across webhooks, the WebSocket and the REST catch-up endpoint.
Setting up Live Tennis API webhooks
Register a webhook with your API key:
curl -X POST "https://api.livetennisapi.com/api/public/v1/webhooks" \
-H "X-API-Key: $LIVE_TENNIS_API_KEY" \
-H "Content-Type: application/json" \
-d '{"url": "https://example.com/webhooks/live-tennis-api", "events": ["score", "point"]}'
The 201 response includes the webhook's id and its secret. Store the secret straight away, for example as LIVE_: this is the only time it is shown, and GET /webhooks never returns it. A fourth webhook on the same key returns 409 webhook_limit, so delete one first with DELETE /webhooks/{webhookId}.
The URL must be HTTPS and publicly routable, and it must answer directly: redirects are disabled, so a URL that redirects from http to https, or adds a trailing slash, fails every delivery.
Securing Live Tennis API webhooks
Read X-LTAPI-Signature, require and strip sha256=, compute an HMAC-SHA256 of the raw body with the webhook secret, and compare in constant time.
const crypto = require('crypto');
const express = require('express');
function verifyLiveTennisSignature(rawBody, header, secret) {
const match = /^sha256=([0-9a-f]{64})$/i.exec(typeof header === 'string' ? header : '');
if (!match || !secret) return false; // check the hex shape before Buffer.from(x, 'hex')
const expected = crypto.createHmac('sha256', secret).update(rawBody).digest();
const received = Buffer.from(match[1], 'hex');
return received.length === expected.length && crypto.timingSafeEqual(received, expected);
}
const app = express();
app.post('/webhooks/live-tennis-api', express.raw({ type: '*/*' }), (req, res) => {
const secret = process.env.LIVE_TENNIS_API_WEBHOOK_SECRET;
if (!secret) return res.sendStatus(500); // fail closed
if (!verifyLiveTennisSignature(req.body, req.get('x-ltapi-signature'), secret)) {
return res.sendStatus(401);
}
const frame = JSON.parse(req.body.toString('utf8'));
const frame_type = frame.type || req.get('x-ltapi-event');
// Queue the frame and return straight away: the timeout is about 3 seconds.
res.sendStatus(200);
});
The same check in Python:
import hashlib
import hmac
def verify_live_tennis_signature(raw_body: bytes, header, secret: str) -> bool:
if not header or not secret or not header.startswith("sha256="):
return False
expected = hmac.new(secret.encode("utf-8"), raw_body, hashlib.sha256).hexdigest()
# Compare bytes so a non-ASCII header fails verification instead of raising.
received = header[len("sha256="):].lower().encode("utf-8")
return hmac.compare_digest(received, expected.encode("utf-8"))
Live Tennis API suggests rejecting stale replays using X-LTAPI-Timestamp. Because that header is not signed, anyone replaying a captured request can change it, so it is a weak check at best. Deduplicate point frames on seq instead.
Make Live Tennis API webhooks production-ready. Hookdeck Event Gateway answers each delivery as soon as it arrives, verifies the
X-LTAPI-Signatureheader, and queues every score and point frame for your service, so a slow handler never misses the three-second window.
Live Tennis API webhook limitations and pain points
At-most-once delivery
The Problem: A frame that your endpoint doesn't accept in time is gone.
Why It Happens: Live Tennis API makes a single attempt per frame with a timeout of about three seconds, and has no retries or replay. A deploy, a cold start or a slow database write is enough to lose a frame.
Workarounds:
- Acknowledge first and process afterwards, so the response never depends on your own work.
- For point frames, track the last
seqper match and fetch gaps fromGET /matches/{matchId}/points?after_.seq= - For score frames, wait for the next commit or read
GET /matches/{matchId}/score.
How Hookdeck Can Help: Hookdeck accepts each delivery straight away and stores it, then delivers it to your service with automatic retries. Your own downtime then delays frames instead of losing them, as long as Hookdeck can be reached.
25 failures and the webhook switches off
The Problem: Deliveries stop, and nothing comes back until you register the webhook again.
Why It Happens: After 25 consecutive failures, Live Tennis API sets enabled to false and records last_error. There is no re-enable call: you delete the webhook and register a new one, which also means a new secret. During a busy day of matches, 25 failures can happen in seconds.
Workarounds:
- Poll
GET /webhooksand alert whenenabledisfalseorconsecutive_failuresclimbs. - Script re-registration so you can restore the webhook and deploy the new secret quickly.
How Hookdeck Can Help: With Hookdeck in front, Live Tennis API delivers to Hookdeck rather than to your service, so an outage on your side doesn't count towards the 25 failures. If you do re-register, you update the secret on one Hookdeck source.
The timestamp isn't signed
The Problem: A replay check built on X-LTAPI-Timestamp can be bypassed.
Why It Happens: The signature covers only the body. The timestamp is a separate, unsigned header.
Workarounds:
- Deduplicate on
seqfor point frames, and treat score frames as idempotent state. - Keep the endpoint on HTTPS.
How Hookdeck Can Help: Hookdeck's deduplication can drop repeated frames based on body fields you choose, such as match_id with point.seq.
Best practices
Verify against the raw body
The HMAC covers the exact bytes sent. Use express.raw() in Express, await request.text() in a Next.js route handler and await request.body() in FastAPI, and parse only after the check passes.
Treat score frames as state and point frames as events
A score frame replaces whatever you held before, so apply the latest one and move on. A point frame adds to a sequence, so store it by match_id and seq and fill gaps from the points endpoint.
Respond in well under three seconds
Do nothing but verify and enqueue before you return 200. See why to process webhooks asynchronously.
Keep the URL redirect-free
Register the exact URL that serves the handler, with the right scheme, host and path. Redirects are not followed.
Watch webhook health
Read last_delivery_at, consecutive_failures and last_error from GET /webhooks and alert before the 25-failure limit is reached.
Conclusion
Live Tennis API signs each frame with X-LTAPI-Signature: sha256= and a hex HMAC-SHA256 of the raw body. Store the secret from the registration response, verify against the raw bytes, answer within three seconds, and build a catch-up path for point frames, because delivery is at-most-once and 25 failures in a row disable the webhook.
Hookdeck Event Gateway sits in front of your receiver, checks every signature, and holds each frame until your service has processed it.
Get started with Hookdeck for free and handle Live Tennis API webhooks reliably in minutes.