Agent skill
Live Tennis API Webhooks Skill
Receive and verify Live Tennis API webhooks. Use when setting up Live Tennis API webhook handlers, debugging X-LTAPI-Signature HMAC-SHA256 verification, registering a webhook with POST /webhooks, or handling live tennis frames like score, point, break_point, and break_point_result.
Install this skill
npx skills add hookdeck/webhook-skills --skill live-tennis-api-webhooks
When to Use This Skill
- How do I receive Live Tennis API webhooks (live scores, points, break points)?
- How do I verify the
X-LTAPI-Signatureheader? - How do I register a webhook with
POST /webhooksand keep the signing secret? - How do I handle
score,point,break_pointorbreak_point_resultframes? - Why is my Live Tennis API webhook signature verification failing?
How Live Tennis API Delivers Webhooks
You register an HTTPS URL with POST /webhooks (ULTRA plan, direct API key only, max 3 webhooks per key). The 201 response contains a secret that is shown exactly once: store it as LIVE_. Each delivery is one POST of the same frame the WebSocket feed sends. Delivery is best-effort and at-most-once: one attempt, ~3 second timeout, redirects disabled, and the webhook is disabled after 25 consecutive failures.
Verification (core)
Every request carries X-LTAPI-Signature: sha256=<hex>, the hex HMAC-SHA256 of the raw request body keyed with your webhook secret. Require the sha256= prefix and strip it. X-LTAPI-Timestamp (Unix seconds) is sent but is not signed, so don't build a replay check on it. There is no webhook SDK, so verify manually against the raw body (don't parse JSON first).
const crypto = require('crypto');
function verifyLiveTennisSignature(rawBody, header, secret) {
const match = /^sha256=([0-9a-f]{64})$/i.exec(typeof header === 'string' ? header : '');
if (!match) return false; // validate hex before Buffer.from(x, 'hex'), which silently truncates
const expected = crypto.createHmac('sha256', secret).update(rawBody).digest();
return crypto.timingSafeEqual(Buffer.from(match[1], 'hex'), expected);
}
import hashlib, hmac
def verify_live_tennis_signature(raw_body: bytes, header: str, secret: str) -> bool:
if not header.startswith("sha256="):
return False
expected = hmac.new(secret.encode(), raw_body, hashlib.sha256).hexdigest()
# compare bytes so a non-ASCII header fails verification instead of raising
return hmac.compare_digest(header[len("sha256="):].lower().encode(), expected.encode())
For complete handlers with route wiring, event dispatch, and tests, see:
- examples/express/ (use
express.raw({ type: '*/*' })so any content type verifies)- examples/nextjs/
- examples/fastapi/
Event Types (X-LTAPI-Event)
The X-LTAPI-Event header names the frame type; the JSON body normally carries the same value in type, and the examples dispatch on the (signed) body type, falling back to the header.
| Event | Description |
|---|---|
score | Complete current score after every accepted live-score commit (a missed one self-corrects on the next) |
point | One played point, with seq, winner, and the score after the point (an event, not a state) |
break_point | A break point just arose (server, returner, break_points, win_probability_p1) |
break_point_result | A break point resolved (outcome is held or broken) |
For payload details, see references/overview.md.
Important Headers
| Header | Description |
|---|---|
X-LTAPI-Signature | sha256=<hex> HMAC-SHA256 of the raw body |
X-LTAPI-Event | Frame type (score, point, break_point, break_point_result) |
X-LTAPI-Timestamp | Unix seconds at send time. Not signed: treat as informational |
Environment Variables
LIVE_TENNIS_API_WEBHOOK_SECRET=your_webhook_secret # `secret` from the POST /webhooks 201 response
Local Development
# Start tunnel (no account needed)
npx hookdeck-cli listen 3000 live-tennis-api --path /webhooks/live-tennis-api
Reference Materials
- references/overview.md - Webhook concepts, frames, delivery semantics
- references/setup.md - Registering a webhook and storing the secret
- references/verification.md - Signature verification details and gotchas