# How to Send Webflow Form Submission Alerts to a Shared Room

> Verify one selected Webflow form webhook, discard submitted fields, and send fixed shared-phone copy with a stable replay key.

- Published: 2026-11-10
- Author: PingRoom
- Category: Automation
- Topics: Webflow form submission notification, Webflow webhook, Webflow phone alert, webhook push notifications
- Canonical: https://pingroom.io/blog/webflow-form-submission-push-notifications
- Cover image: https://pingroom.io/blog/webflow-form-submission-push-notifications-cover.webp
- Cover description: Two PingRoom avatars guide one sealed form token toward a wooden verification gate while Pingo watches beside a closed box.

**To send a shared phone alert for one Webflow form, add a signed `form_submission` webhook, verify its signature in a small server-side relay, discard the submitted fields, and forward fixed copy to a private PingRoom Room.** Webflow keeps the response. The Room receives only **New Webflow form** and **Open Webflow to review the selected form.**

This is a webhook recipe, not a native Webflow integration. It suits a small group that needs the same low-volume attention signal. It does not turn the Room into a second lead database.

## Choose the smallest route that works

Webflow can already store native form submissions and send email notifications. Start there when the right people work from the same inbox or Webflow Workspace. Add another system only when it solves a separate job.

| Need                                                                     | Better starting point                                                                                     |
| ------------------------------------------------------------------------ | --------------------------------------------------------------------------------------------------------- |
| One person or a few named recipients need the full submission            | [Webflow email notifications](https://help.webflow.com/hc/en-us/articles/33961344521235-Form-submissions) |
| A no-code workflow should route form fields into another business app    | [Webflow's Zapier connection](https://webflow.com/integrations/zapier)                                    |
| A small group needs one fixed, privacy-minimal phone signal              | The signed relay and PingRoom workflow below                                                              |
| The form drives assignment, qualification, service levels, or escalation | Keep that logic in a CRM or workflow system built for it                                                  |

The direct relay is useful when you want exact signature checks, replay handling, and strict control over what leaves Webflow. It receives the full Webflow event, but the PingRoom request contains none of the submitted fields.

## What you need

- One native Webflow form on a published site.
- The Webflow site ID and form ID for the exact form you intend to accept.
- A server-side HTTPS relay that can preserve the raw request body and store secrets.
- A durable queue or database with a unique constraint for one replay key.
- A private PingRoom Room whose owner has **PingRoom Pro**. Incoming webhooks are Pro connections.
- A test submission that contains invented data, not a real person's details.

Webflow's current form settings can keep the submission in Webflow while also sending it to a webhook. A **custom action** behaves differently and can bypass Webflow's own form processing, so do not substitute it without reviewing the storage and notification consequences.

## Prepare the PingRoom destination

Open [your Rooms in the PingRoom browser app](/app/rooms), choose the destination Room, then open **Settings → Connections → From another app**.

1. Add an incoming webhook and name it after the Webflow site and form.
2. Keep **Quick Ping** on **Custom message**.
3. Save and test it once.
4. Store the complete PingRoom webhook URL in the relay's protected server configuration.

The URL contains a secret. Do not put it in Webflow form fields, browser code, a public repository, screenshots, analytics, or logs. The relay is the only component that should know both the Webflow verification secret and the PingRoom URL. The [incoming-webhook reference](/documents/webhooks) documents PingRoom's current request fields, limits, cooldown behavior, idempotency, and response shape.

## Add one selected Webflow webhook

Webflow's current [form setup guide](https://help.webflow.com/hc/en-us/articles/33961347548563-How-do-I-add-forms-in-Webflow) says to select the form, open its settings, and add **Webhook** under **Send to**. Enter the relay URL, save the form, and copy the one-time secret key.

There is a documentation wrinkle worth testing instead of guessing about. That current form guide says the form-level webhook has a signing secret, while Webflow's broader developer guide still contains an older note that dashboard-created webhooks do not include signature headers. **Do not enable the PingRoom consumer until a controlled submission reaches your relay with both signature headers and passes verification.** If either header is absent, stop and create the webhook through Webflow's API or ask Webflow support which creation path applies to your site. Never fall back to accepting an unsigned public request.

Webflow's [webhook developer guide](https://developers.webflow.com/data/docs/working-with-webhooks) documents two request headers:

- `x-webflow-timestamp`, a Unix timestamp in milliseconds;
- `x-webflow-signature`, an HMAC-SHA256 signature over `timestamp + ":" + request body`.

Webflow recommends rejecting requests outside a five-minute window. The relay below also rejects timestamps more than five minutes in the future, which keeps a bad clock or captured future-dated request from extending the acceptance window.

## Verify first, then discard the fields

The relay must verify the signature against the unmodified body before parsing JSON. After verification, it accepts only one site, one form, and the `form_submission` event. It uses Webflow's stable submission ID to derive an opaque replay key, then queues fixed PingRoom copy.

This is the exact dependency-free core used by the controlled checks for this article:

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

function requiredEnv(name) {
  const value = process.env[name];
  if (!value) throw new Error(`Missing ${name}`);
  return value;
}

const EXPECTED_SITE_ID = requiredEnv("WEBFLOW_SITE_ID");
const EXPECTED_FORM_ID = requiredEnv("WEBFLOW_FORM_ID");
const WEBFLOW_WEBHOOK_SECRET = requiredEnv("WEBFLOW_WEBHOOK_SECRET");
const REPLAY_SECRET = requiredEnv("WEBFLOW_REPLAY_SECRET");
const MAX_AGE_MS = 300_000;

function sameHex(received, expected) {
  if (!/^[a-f0-9]{64}$/i.test(received ?? "")) return false;
  const left = Buffer.from(received, "hex");
  const right = Buffer.from(expected, "hex");
  return left.length === right.length && timingSafeEqual(left, right);
}

export async function receiveWebflow({ headers, rawBody, nowMs, enqueueOnce }) {
  const timestamp = headers["x-webflow-timestamp"];
  const signature = headers["x-webflow-signature"];
  if (!/^\d{13}$/.test(timestamp ?? "")) {
    return { status: 400, outcome: "invalid_timestamp" };
  }

  const expectedSignature = createHmac("sha256", WEBFLOW_WEBHOOK_SECRET)
    .update(`${timestamp}:${rawBody}`)
    .digest("hex");
  if (!sameHex(signature, expectedSignature)) {
    return { status: 401, outcome: "invalid_signature" };
  }

  if (Math.abs(nowMs - Number(timestamp)) > MAX_AGE_MS) {
    return { status: 400, outcome: "stale_timestamp" };
  }

  let event;
  try {
    event = JSON.parse(rawBody);
  } catch {
    return { status: 400, outcome: "invalid_json" };
  }

  if (
    event?.triggerType !== "form_submission" ||
    event?.payload?.siteId !== EXPECTED_SITE_ID ||
    event?.payload?.formId !== EXPECTED_FORM_ID
  ) {
    return { status: 200, outcome: "ignored" };
  }

  const submissionId = event?.payload?.id;
  if (!/^[a-f0-9]{24}$/i.test(submissionId ?? "")) {
    return { status: 400, outcome: "invalid_submission_id" };
  }

  const digest = createHmac("sha256", REPLAY_SECRET)
    .update(`${EXPECTED_SITE_ID}:${EXPECTED_FORM_ID}:${submissionId}`)
    .digest("hex");
  const replayKey = `webflow-form-${digest}`;
  const pingRoomBody = {
    action: null,
    title: "New Webflow form",
    message: "Open Webflow to review the selected form.",
    correlation_id: replayKey,
  };

  try {
    const inserted = await enqueueOnce(replayKey, {
      headers: {
        "content-type": "application/json",
        "idempotency-key": replayKey,
      },
      body: JSON.stringify(pingRoomBody),
    });
    return { status: 200, outcome: inserted ? "queued" : "replay" };
  } catch {
    return { status: 503, outcome: "queue_unavailable" };
  }
}
```

Wrap this core in your server framework without changing two boundaries:

1. capture the raw bytes before any JSON middleware rewrites the body;
2. return HTTP 200 only after `enqueueOnce` has durably stored the item or confirmed that the same replay key already exists.

Webflow treats non-200 responses, redirects, certificate failures, and timeouts as failures. Its current guide says it retries a failed webhook up to three times at ten-minute intervals and may deactivate repeatedly failing webhooks. A process-local `Map` is enough for a test, not for production queueing or duplicate control.

The queue consumer posts the saved request to PingRoom. If that attempt fails, it retries the same body with the same `Idempotency-Key`. A changed body needs a new key; reusing the old key with different copy should produce an idempotency conflict.

## Keep form data out of the Room

Webflow's event can contain the form name, submission timestamp, site ID, form ID, submission ID, and every submitted field. The relay needs only the site ID, form ID, and submission ID after signature verification.

Do not forward names, email addresses, phone numbers, free-text messages, file links, IP-related data, campaign fields, or hidden form fields. Do not use the Room Ping to decide whether a submission is legitimate, urgent, qualified, or safe to act on. Review the original response and any spam status in Webflow or the system that owns that decision.

The HMAC-derived replay key also keeps Webflow's raw identifiers out of PingRoom. The separate `WEBFLOW_REPLAY_SECRET` prevents the identifier from being a plain guessable hash.

## Rehearse the full boundary

Publish the form to a staging or controlled live domain, then use invented values for this rehearsal:

1. Submit the selected form once and confirm Webflow stores the response.
2. Confirm the relay verifies the signature before parsing or logging the body.
3. Inspect the durable queue item. It should contain only the fixed title, fixed message, correlation ID, and idempotency header.
4. Confirm the PingRoom Room shows **New Webflow form** and **Open Webflow to review the selected form.**
5. Replay the same signed event and confirm the queue reports a replay without adding another item.
6. Change the submitted fields but keep the same submission ID; the Room should still receive no new Ping and no submitted data.
7. Send a validly signed event for another site, form, or trigger type and confirm the relay ignores it.
8. Test a bad signature, a stale timestamp, a future timestamp, malformed JSON, and a queue failure. None may create a Ping.
9. Disable the PingRoom webhook and confirm the consumer preserves its retry record instead of pretending delivery succeeded.

The controlled fixture for this article covers those boundaries with Webflow-shaped signed requests, a queue stand-in, and a local PingRoom contract mock. It uses no live Webflow site, public relay, customer response, PingRoom Room, or phone.

![Two PingRoom avatars separate an accepted blank token from an identical replay at a wooden gate while Pingo watches beside a closed box.](/blog/webflow-form-submission-push-notifications-1.webp)

Pingo the Pigeon, PingRoom's fictional Chief Marketing Officer, is watching the duplicate remain behind the gate. He has not read the form, joined the private Room, or decided that the submission matters. The human team owns the form, privacy choices, workflow, and response.

## Read the result correctly

A successful Ping means the configured Webflow webhook passed the relay's checks, the replay item was queued, and PingRoom accepted the fixed request. It does not prove:

- that the form was completed by a real person;
- that the response is accurate, safe, urgent, or qualified;
- that somebody reviewed or answered it;
- that a downstream CRM or workflow ran;
- or that every phone displayed a notification.

Webflow remains the record for the submitted response. PingRoom records the short shared signal and sends it through its normal delivery path. Connectivity, operating-system policy, notification permissions, Focus settings, Room mutes, and device state can still affect what appears.

If that boundary fits your team, [create or choose the private Room](/app/rooms), add its incoming webhook, and rehearse one invented Webflow submission before accepting real responses.

_Illustrations are AI-generated editorial scenes featuring [Pingo the Pigeon](/Pingo) and approved PingRoom avatars. The people and situation are fictional examples, not customers or product screens. The human team is responsible for product claims and decisions._
