# 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-Signature` header?
* How do I register a webhook with `POST /webhooks` and keep the signing secret?
* How do I handle `score`, `point`, `break_point` or `break_point_result` frames?
* 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_TENNIS_API_WEBHOOK_SECRET`. 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).

```javascript
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);
}

```

```python
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/](https://github.com/hookdeck/webhook-skills/tree/main/skills/live-tennis-api-webhooks/examples/express/) (use `express.raw({ type: '*/*' })` so any content type verifies)
> * [examples/nextjs/](https://github.com/hookdeck/webhook-skills/tree/main/skills/live-tennis-api-webhooks/examples/nextjs/)
> * [examples/fastapi/](https://github.com/hookdeck/webhook-skills/tree/main/skills/live-tennis-api-webhooks/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](https://github.com/hookdeck/webhook-skills/blob/main/skills/live-tennis-api-webhooks/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

```bash
LIVE_TENNIS_API_WEBHOOK_SECRET=your_webhook_secret   # `secret` from the POST /webhooks 201 response

```

## Local Development

```bash
# Start tunnel (no account needed)
npx hookdeck-cli listen 3000 live-tennis-api --path /webhooks/live-tennis-api

```

## Reference Materials

* [references/overview.md](https://github.com/hookdeck/webhook-skills/blob/main/skills/live-tennis-api-webhooks/references/overview.md) - Webhook concepts, frames, delivery semantics
* [references/setup.md](https://github.com/hookdeck/webhook-skills/blob/main/skills/live-tennis-api-webhooks/references/setup.md) - Registering a webhook and storing the secret
* [references/verification.md](https://github.com/hookdeck/webhook-skills/blob/main/skills/live-tennis-api-webhooks/references/verification.md) - Signature verification details and gotchas