How to Send Typeform Response Push Notifications to a Shared Room
Verify Typeform's signed webhook, keep answers private, and send one fixed Ping when a completed response arrives.

To send a Typeform response notification to a shared phone-alert Room, connect Typeform's full-response webhook to a small HTTPS relay. The relay should verify Typeform's signature, discard the answer content, and send one fixed message to a PingRoom incoming webhook.
This guide sends New Typeform response — Workshop request received a response. The submitted answers stay in Typeform. It is a webhook recipe you configure, not a native Typeform integration.
Choose email or a shared Room
Typeform can already email the account owner when a response arrives. Use that first when one person checks email and no shared phone alert is needed.
| Need | Better fit |
|---|---|
| One Typeform account owner wants an email | Typeform's built-in self notification |
| A selected team should get one short phone alert | The signed-webhook relay below |
| Another system needs response fields for processing | An approved backend or automation tool with its own data controls |
The Room should contain only the people who need the signal. It does not replace Typeform's response record, permissions, review process, or retention policy.
Keep the response in Typeform
Approve a short, non-sensitive form label such as Workshop request, Volunteer signup, or Equipment check. The relay uses that fixed label and never copies answer fields into the Ping.
For an illustrative workshop request, the Room receives:
New Typeform response
Workshop request received a response.
The team follows its normal authorized route to Typeform for the details. The alert does not name the respondent or claim that anyone reviewed, approved, or completed the request.
Pingo the Pigeon, our fictional CMO, appears here beside one blank signal token and a closed archive box. He cannot read the response, access the private Room, or send a Ping. The human team chooses the copy, Room membership, and follow-up.
Prepare both webhook ends
The destination Room's owner needs active PingRoom Pro for incoming webhooks. Room members who want native alerts need PingRoom on a supported device with notifications allowed.
Follow the incoming webhook setup: open the Room's Settings → Connections → From another app, add a webhook named Typeform, choose Custom message, and copy its URL. The complete URL is a credential. Store it as a server-side secret; never place it in form copy, client-side code, source control, screenshots, or logs.
Your relay also needs a separate random TYPEFORM_WEBHOOK_SECRET and a fixed TYPEFORM_FORM_LABEL. Typeform sends the signature in the Typeform-Signature header. Its secure-webhook guide requires an HMAC-SHA256 over the raw request body, Base64 encoded and prefixed with sha256=.
In Typeform, open the form's Connect area, add your relay's HTTPS URL as a webhook, enable full responses, and set the same webhook secret. Send Typeform's test request before accepting real submissions. Typeform's webhook setup guide shows the current controls.
Verify, minimize, and forward
This Node-compatible handler verifies the untouched body before parsing it. It uses only Typeform's form ID and response token for event identity; it never reads the answers array.
import { createHash, createHmac, timingSafeEqual } from "node:crypto"; function secureEqual(left, right) { const a = Buffer.from(left); const b = Buffer.from(right); return a.length === b.length && timingSafeEqual(a, b); } function validPingRoomUrl(value) { return /^https:\/\/api\.pingroom\.io\/api\/webhooks\/[^/?#\s]+\/[^/?#\s]+$/.test( value || "", ); } export async function handleTypeform(request, env, fetchImpl = fetch) { if ( !env.TYPEFORM_WEBHOOK_SECRET || !validPingRoomUrl(env.PINGROOM_WEBHOOK_URL) ) { return new Response("server configuration error", { status: 500 }); } const rawBody = await request.text(); const received = request.headers.get("typeform-signature") || ""; const expected = "sha256=" + createHmac("sha256", env.TYPEFORM_WEBHOOK_SECRET) .update(rawBody) .digest("base64"); if (!secureEqual(received, expected)) { return new Response("invalid signature", { status: 401 }); } let event; try { event = JSON.parse(rawBody); } catch { return new Response("invalid JSON", { status: 400 }); } if (event.event_type !== "form_response") { return new Response("ignored", { status: 202 }); } const formId = event.form_response?.form_id; const responseToken = event.form_response?.token; const label = (env.TYPEFORM_FORM_LABEL || "").trim(); if ( typeof formId !== "string" || !formId || typeof responseToken !== "string" || !responseToken || !label || Array.from(label).length > 70 || /[\r\n\t]/.test(label) ) { return new Response("invalid event configuration", { status: 400 }); } const digest = createHash("sha256") .update(`${formId}:${responseToken}`) .digest("hex"); const eventKey = `typeform-${digest}`; const pingBody = JSON.stringify({ action: null, title: "New Typeform response", message: `${label} received a response.`, correlation_id: eventKey, }); let pingResponse; try { pingResponse = await fetchImpl(env.PINGROOM_WEBHOOK_URL, { method: "POST", headers: { "Content-Type": "application/json", "Idempotency-Key": eventKey, }, body: pingBody, redirect: "error", signal: AbortSignal.timeout(8000), }); } catch { return new Response("upstream unavailable", { status: 502 }); } let accepted = false; try { accepted = (await pingResponse.json()).success === true; } catch { // A non-JSON response cannot confirm acceptance. } if (pingResponse.status !== 200 || !accepted) { return new Response("upstream unavailable", { status: 502 }); } return new Response("accepted", { status: 200 }); }
Typeform's example payload identifies a full response with event_type: "form_response" and supplies a response token. Hashing that token with the form ID creates a stable PingRoom Idempotency-Key without exposing either identifier in Room history.
If Typeform repeats the same delivery, the fixed body and stable key let PingRoom return the previously accepted result instead of creating another Ping. Do not add a current timestamp to the body: that would make an exact retry look different.

AI-generated editorial illustration. Pingo is PingRoom's fictional mascot; the people and workflow are illustrative.
Test the privacy and retry boundaries
Typeform expects the endpoint to return a successful response promptly and may retry failed deliveries. Its webhook troubleshooting guide also warns that duplicate deliveries can occur and recommends the response token for duplicate handling.
Before using a real form, send a harmless test response and check:
- A valid signed
form_responseproduces one Ping with the fixed title and label. - The Ping body contains no answers, respondent name, email, response URL, or Typeform secret.
- Replaying the identical signed body produces no second Ping.
- A changed byte or wrong secret returns
401and sends nothing to PingRoom. - A PingRoom rejection makes the relay return a non-2xx response so Typeform can retry.
For this article, we ran the exact embedded handler locally with signed Typeform fixtures and a mock of PingRoom's documented webhook contract. The check covered valid delivery, exact replay, changed events, partial-response ignore, missing or altered signatures, malformed JSON, invalid configuration, PingRoom rejection, malformed upstream responses, and network failure. It made no live Typeform, PingRoom, or phone request.
Know what acceptance means
Typeform's webhook endpoint must use HTTPS. Its timeout and retry behavior still apply. PingRoom permits up to four incoming webhooks per Room, applies the Room's webhook cooldown, and enforces request limits described in the webhook documentation.
A PingRoom response with HTTP 200 and success: true means the Ping was accepted and queued. It does not guarantee that every device displayed an alert under every network, Focus, mute, battery, or notification setting.
If the form controls healthcare, safeguarding, payment, legal, emergency, or regulated work, keep the approved case system, access controls, audit record, and escalation path authoritative. This alert is an attention cue, not proof that a response was reviewed or acted on.
Start with one harmless form and a small PingRoom Room. If your intake lives in Google Forms instead, use the separate Google Forms response-notification recipe.
PingRoom
Short-form communication.


