# Lobsite-Webhooks

Requests from Lobsite-Webhooks/1 deliver form submissions from a Lobsite site to an address its owner set up. How to verify them, and how to stop them.

User agent: `Lobsite-Webhooks/1`

Page: https://lobsite.com/bots/lobsite-webhooks

Lobsite-Webhooks is the user agent of the webhooks Lobsite sends. A Lobsite site with a form can send each submission to a webhook address its owner set in `lobsite.toml`, and these requests are those deliveries. If you see it in your logs, someone pointed a form's webhook at your server: usually you, from your own Lobsite site.

## What a request looks like

Each accepted, non-spam form submission is one `POST` with a JSON body:

```
POST /hooks/contact HTTP/1.1
Content-Type: application/json
User-Agent: Lobsite-Webhooks/1
X-Lobsite-Event: form.submission
X-Lobsite-Delivery: dlv_3m9q…
X-Lobsite-Signature: t=1760372438,v1=5257a869…
```

```json
{
  "type": "form.submission",
  "id": "sub_8x2k…",
  "site": "quiet-fox-4821",
  "form": "contact",
  "data": { "email": "ana@example.com", "message": "Hi!" },
  "created_at": "2026-10-13T16:20:38Z"
}
```

Webhooks only go to `https://` addresses that don't resolve to private or internal networks.

## Check that a request is really from Lobsite

The user agent can be faked; the signature can't. `X-Lobsite-Signature` has a timestamp `t` and `v1`, the hex HMAC-SHA256 of `"<t>.<raw body>"` with the form's webhook secret, shown once in the Lobsite dashboard. Compute the same value from the raw body, compare in constant time, and reject a `t` older than 5 minutes.

```js
import { createHmac, timingSafeEqual } from "node:crypto";

function verified(secret, header, rawBody) {
  const { t, v1 } = Object.fromEntries(header.split(",").map((p) => p.split("=")));
  if (Math.abs(Date.now() / 1000 - Number(t)) > 300) return false;
  const expected = createHmac("sha256", secret).update(`${t}.${rawBody}`).digest("hex");
  return v1?.length === expected.length && timingSafeEqual(Buffer.from(v1), Buffer.from(expected));
}
```

## Retries

A `2xx` answer within 10 seconds counts as delivered. Anything else is tried again 5 times: after 1 minute, 5 minutes, 30 minutes, 2 hours and 12 hours. `X-Lobsite-Delivery` stays the same across the retries, so you can skip a delivery you've already handled.

## Frequently asked questions

### How do I stop these requests?

If the site is yours, remove `webhook` from the form in `lobsite.toml` and deploy. If it isn't, answer with an error: each submission is tried 6 times in all and then dropped. If they keep coming, write to abuse@lobsite.com with the `X-Lobsite-Delivery` value and we'll look into it.

### Which plans send webhooks?

Webhooks are part of the Standard plan. Sites on the free plan get email notifications instead. See the [forms docs](https://lobsite.com/docs/forms).

### Why do I get the same submission twice?

Your server answered too slowly or with an error the first time, so it was retried. Skip deliveries whose `X-Lobsite-Delivery` you've seen.

