# Notifications and webhooks

> Send each form's new submissions to email, Slack, Discord, Teams, Telegram, WhatsApp, Google Sheets or a signed webhook, with retries.

Source: https://sitebackend.com/docs/forms/notifications · Updated: 2026-10-03

SiteBackend can tell you about every new submission on the channels your team already uses. Notifications are set up **per form**: open a form and go to its **Notifications** tab.

## Which channels are supported?

| Channel         | What you need                                                                           |
| --------------- | --------------------------------------------------------------------------------------- |
| Email           | One or more email addresses.                                                            |
| Slack           | An [incoming webhook URL](https://api.slack.com/messaging/webhooks).                    |
| Discord         | A channel webhook URL (**Channel settings → Integrations → Webhooks**).                 |
| Microsoft Teams | An incoming webhook URL for the channel.                                                |
| Telegram        | A bot token from @BotFather and a chat ID.                                              |
| WhatsApp        | A WhatsApp Cloud API access token, phone number ID and recipient number.                |
| Google Sheets   | A spreadsheet shared with SiteBackend's service account; each submission becomes a row. |
| Webhook         | Any HTTPS URL. Requests can be signed so you can verify them.                           |

Secrets such as tokens and webhook URLs are stored encrypted and never shown again after saving.

## How do I add a channel?

In **Form → Notifications**, click **Add channel**, pick a type and fill in its details. SiteBackend sends a test message straight away and marks the channel **Verified** when it arrives. Each channel has **Test**, **Edit**, delete and an on/off switch, and each form can have up to 10 channels. Editing a channel's destination clears **Verified** until you test it again.

Every new submission that reaches the form's inbox is sent to each of its active channels. Spam never is. To notify the same Slack channel from several forms, add it to each form.

## What about the monthly limit?

On the Free plan, SiteBackend emails the workspace's owners and admins at 80% of the monthly limit and again when it's reached. These alerts aren't tied to a form, so they don't use form channels. Submissions past the limit are saved but hidden, and don't send notifications. See [plans and limits](https://sitebackend.com/docs/workspace/plans-and-limits).

## What about new testimonials?

Testimonials don't use form channels. Owners and admins get an email for each new testimonial, with a link to approve it. Turn it off with **Email me new testimonials** on the site's **Testimonials → Collect** page. See [collecting testimonials](https://sitebackend.com/docs/testimonials/collecting#emails-for-new-testimonials).

## Can the person who submitted get an email too?

Yes, on Pro: turn on **Auto-reply** in the form's settings. See forms.

## What if a channel is down?

Deliveries are retried automatically, up to 8 attempts over about 11 hours. Each form's **Notifications** tab has a delivery log with every delivery, its status and the last error. Failed deliveries can also be retried by hand from there or from the submission.

## Webhooks

Webhook channels send a `POST` (or `PUT`) with a JSON body:

```json
{
  "id": "delivery id",
  "event": "SUBMISSION_CREATED",
  "createdAt": "2026-09-26T10:00:00.000Z",
  "data": {
    "event": "SUBMISSION_CREATED",
    "form": { "id": "…", "publicId": "…", "name": "Contact form" },
    "submission": { "id": "…", "data": { "email": "jane@example.com", "message": "Hi" }, "createdAt": "…" }
  }
}
```

Every request includes these headers:

| Header                    | Value                                                            |
| ------------------------- | ---------------------------------------------------------------- |
| `x-sitebackend-event`     | `SUBMISSION_CREATED`                                             |
| `x-sitebackend-delivery`  | A unique delivery ID; use it to ignore duplicates.               |
| `x-sitebackend-timestamp` | Unix time in seconds.                                            |
| `x-sitebackend-signature` | `t=<timestamp>,v1=<hex>`, when the channel has a signing secret. |

Your endpoint should respond with a `2xx` status. Anything else is retried.

## How do I verify a webhook signature?

Compute an HMAC-SHA256 of `` `${timestamp}.${rawBody}` `` with your signing secret, compare it to `v1`, and reject old timestamps:

```js title="verify.js"
import crypto from "node:crypto";

export function verify(rawBody, header, secret) {
  const { t, v1 } = Object.fromEntries(header.split(",").map((p) => p.split("=")));
  const expected = crypto.createHmac("sha256", secret).update(`${t}.${rawBody}`).digest("hex");
  const fresh = Math.abs(Date.now() / 1000 - Number(t)) < 300; // 5 minutes
  return fresh && crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(v1));
}
```

> **Warning:** Verify against the **raw** request body, before any JSON parsing. Re-serializing the JSON can change it and break the signature.
