# Baseten Webhooks

## When to Use This Skill

* How do I receive Baseten webhooks for async inference results?
* How do I verify the `X-BASETEN-SIGNATURE` header?
* How do I handle `async_request_completed` events, including failed requests?
* Why does my Baseten webhook signature verification fail (raw body, `v1=` prefix)?
* How do I avoid losing model output when Baseten only retries once?

## Setup note

Create a webhook secret in the Baseten Secrets tab and paste it here. Without one, deliveries are unsigned and fail verification.

Store it as `BASETEN_WEBHOOK_SECRET`. Baseten calls your endpoint only for async requests that set `webhook_endpoint` on `/async_predict`.

## Verification (core)

Baseten sends `X-BASETEN-SIGNATURE: v1=<hex>`: a lowercase hex HMAC-SHA256 of the **raw request body bytes**, keyed with the UTF-8 bytes of your webhook secret. Nothing else is signed (no timestamp or id), so there is no replay window to check. During secret rotation the header may hold several comma-separated values; accept if any matches. Validate the hex (64 characters) before decoding, then compare in constant time.

Node:

```javascript
const crypto = require('crypto');

function verify(rawBody, header, secret) {
  if (!header || !secret) return false;
  const expected = crypto.createHmac('sha256', secret).update(rawBody).digest();
  return header.split(',').some((part) => {
    const sig = part.trim().replace(/^v1=/, '');
    if (!/^[0-9a-fA-F]{64}$/.test(sig)) return false;
    return crypto.timingSafeEqual(Buffer.from(sig, 'hex'), expected);
  });
}

```

Python:

```python
import hashlib, hmac, re

def verify(raw_body: bytes, header: str, secret: str) -> bool:
    if not header or not secret:
        return False
    expected = hmac.new(secret.encode(), raw_body, hashlib.sha256).digest()
    for part in header.split(","):
        sig = part.strip().removeprefix("v1=")
        if re.fullmatch(r"[0-9a-fA-F]{64}", sig) and hmac.compare_digest(bytes.fromhex(sig), expected):
            return True
    return False

```

Read the body as bytes before any JSON parsing (Express: `express.raw({ type: '*/*' })`, Next.js: `await request.arrayBuffer()`, FastAPI: `await request.body()`). Missing, malformed or mismatched signature: respond `401`.

> **For complete handlers with route wiring, event dispatch, and tests**, see:
> 
> * [examples/express/](https://github.com/hookdeck/webhook-skills/tree/main/skills/baseten-webhooks/examples/express/)
> * [examples/nextjs/](https://github.com/hookdeck/webhook-skills/tree/main/skills/baseten-webhooks/examples/nextjs/)
> * [examples/fastapi/](https://github.com/hookdeck/webhook-skills/tree/main/skills/baseten-webhooks/examples/fastapi/)

## Event Types

Baseten documents one event type. There is no type header; dispatch on the body's `type` field.

| `type` | Description |
| --- | --- |
| `async_request_completed` | Async inference finished. Success and failure share this type: `data` holds the output (or `null`) and `errors` is empty on success or holds error objects (e.g. `MODEL_PREDICT_ERROR`) on failure |

Log and acknowledge any other `type` with `200`.

## Important Headers

| Header | Description |
| --- | --- |
| `X-BASETEN-SIGNATURE` | `v1=<hex>` HMAC-SHA256 of the raw body; only sent if a webhook secret is configured |
| `X-BASETEN-REQUEST-ID` | The async request ID, for correlating with your `/async_predict` call |

## Delivery Behaviour

Best effort: 2 attempts in total (one retry about 2 s later), 10 s timeout per attempt, retried only on `500`, `502`, `503` and `504`. If both fail, the model output is lost and the request status becomes `WEBHOOK_FAILED`. Respond quickly, return `401` for bad signatures (not retried) and `500` only for transient failures. Persist outputs in your model's `postprocess()` too.

## Environment Variables

```bash
BASETEN_WEBHOOK_SECRET=your_webhook_secret   # Created in the Baseten Secrets tab

```

## Local Development

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

```

## Reference Materials

* [references/overview.md](https://github.com/hookdeck/webhook-skills/blob/main/skills/baseten-webhooks/references/overview.md) - Baseten webhook concepts, payload, delivery
* [references/setup.md](https://github.com/hookdeck/webhook-skills/blob/main/skills/baseten-webhooks/references/setup.md) - Secret and `webhook_endpoint` configuration
* [references/verification.md](https://github.com/hookdeck/webhook-skills/blob/main/skills/baseten-webhooks/references/verification.md) - Signature verification details and gotchas