# Browserbase Webhooks

[Browserbase](https://www.browserbase.com) is a headless browser platform. Its webhooks POST a signed event to your HTTPS endpoint when a [Browserbase Function](https://docs.browserbase.com/platform/functions/overview) invocation or build changes state, so you don't have to poll `GET /v1/functions/invocations/{id}`. Deliveries follow the Standard Webhooks spec.

## When to Use This Skill

* How do I receive Browserbase webhooks?
* How do I verify a Browserbase webhook signature?
* Where do I find my Browserbase webhook signing secret? (Only in the create or rotate response.)
* How do I handle `functions.invocations.completed` and `functions.invocations.failed`?
* Why is my Browserbase `webhook-signature` verification failing?
* How do I rotate a Browserbase webhook secret without dropping deliveries?

## Verification (core)

| Header | Value |
| --- | --- |
| `webhook-id` | Unique message identifier |
| `webhook-timestamp` | Unix seconds when the delivery was sent |
| `webhook-signature` | `v1,<base64 HMAC-SHA256>`, possibly several space-separated entries |

Signed content is `{webhook-id}.{webhook-timestamp}.{raw body}`. The HMAC key is the base64-**decoded** part of the `whsec_` secret. Browserbase's docs say to use a Standard Webhooks library (`standardwebhooks` on npm and PyPI) instead of hand-rolling the HMAC. It handles the constant-time comparison and a 5-minute timestamp tolerance.

```javascript
const { Webhook } = require('standardwebhooks');

// Current secret first; after a rotation the old one keeps verifying for 24h.
const secrets = [process.env.BROWSERBASE_WEBHOOK_SECRET, process.env.BROWSERBASE_WEBHOOK_SECRET_PREVIOUS].filter(Boolean);
if (secrets.length === 0) throw new Error('BROWSERBASE_WEBHOOK_SECRET not set'); // fail closed → 500

function verifyBrowserbaseWebhook(rawBody, headers) { // rawBody: exact bytes, NOT re-serialised JSON
  let lastError;
  for (const secret of secrets) {
    try {
      return new Webhook(secret).verify(rawBody, headers); // strips whsec_, base64-decodes key
    } catch (err) { lastError = err; }
  }
  throw lastError;                                      // → 400 Invalid signature
}

```

Python: `from standardwebhooks.webhooks import Webhook`, then `Webhook(secret).verify(await request.body(), headers)`.

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

## Event Envelope

```json
{
  "id": "85e6ff2d-91de-468a-867c-e0d9e4587063",
  "type": "functions.invocations.completed",
  "resourceId": "a3f1c2d4-7b90-4e51-9c8a-2f6d1e0b4c73",
  "data": { }
}

```

`id` identifies the event and stays the same across retries, so use it as your deduplication key. `type` is the event type, `resourceId` is the invocation or build the event concerns, and `data` holds type-specific fields. Browserbase may add envelope keys but won't rename or remove them, so ignore keys you don't recognize.

## Common Event Types

| Event | Fires when |
| --- | --- |
| `functions.invocations.pending` | Browserbase accepts and queues an invocation |
| `functions.invocations.running` | An invocation starts executing |
| `functions.invocations.completed` | An invocation finishes successfully |
| `functions.invocations.failed` | An invocation fails |
| `functions.builds.running` | A build (publish of your Function code) starts |
| `functions.builds.completed` | A build finishes successfully |
| `functions.builds.failed` | A build fails |

An endpoint receives only the types it subscribes to. Browserbase rejects a subscription to an unknown type when you register the endpoint. See [references/overview.md](https://github.com/hookdeck/webhook-skills/blob/main/skills/browserbase-webhooks/references/overview.md).

## Delivery Semantics

* Return any `2xx` within **15 seconds**. The response body is ignored. A `3xx` redirect counts as a failure, so register the final URL.
* Retries run immediately, then after 5s, 5m, 30m, 2h, 5h, 10h and 10h (eight attempts in total). An endpoint that fails continuously for **five days** is disabled.
* The same event can arrive more than once. Dedupe on the envelope `id`.
* Events aren't ordered: a `completed` event can arrive before `running` for the same invocation. Branch on `type`, not arrival order.

## Environment Variables

```bash
BROWSERBASE_WEBHOOK_SECRET=whsec_...            # required; handlers fail closed without it
BROWSERBASE_WEBHOOK_SECRET_PREVIOUS=            # optional, during the 24h rotation window

```

## Local Development

```bash
# Express / Next.js (use 8000 for FastAPI). No install or account required.
npx hookdeck-cli listen 3000 browserbase --path /webhooks/browserbase

```

Register the printed `https://` URL as the webhook endpoint (Browserbase rejects `http://`), then invoke a Function to trigger an event.

## Reference Materials

* [references/overview.md](https://github.com/hookdeck/webhook-skills/blob/main/skills/browserbase-webhooks/references/overview.md) - Event types, envelope, delivery and retry behaviour
* [references/setup.md](https://github.com/hookdeck/webhook-skills/blob/main/skills/browserbase-webhooks/references/setup.md) - Creating the endpoint (dashboard, SDK, API), storing and rotating the secret
* [references/verification.md](https://github.com/hookdeck/webhook-skills/blob/main/skills/browserbase-webhooks/references/verification.md) - SDK and manual verification, gotchas, debugging