Help · Integrations

Send your leads, scans and photos to other apps

Webhooks pass what happens in QR Walrus to Zapier, Make, Google Sheets or your own server the moment it happens. This guide walks you through each one, shows exactly what we send, and covers the common snags.

Plans
Pro, Business and Enterprise
Events
Form sent on a page · QR code scanned · Guest photo added
Works with
Zapier, Make, Google Sheets, your own server
If it fails
Tried up to 7 times over about 35 hours

What webhooks are

A webhook is how QR Walrus tells another app that something happened. When someone sends a form on your page, scans one of your codes or adds a guest photo, we send the details straight away to an address you choose: a web address from Zapier, Make or your own server. Technically it's a POST request with JSON. That app can then add a row to a spreadsheet, message your team, create a contact in your CRM, or anything else it can do.

  1. 1

    Something happens

    A form is sent, a code is scanned or a guest photo is added.

  2. 2

    We send it

    To your address, within seconds, signed so it can be checked.

  3. 3

    Your app acts

    Adds a row, sends a message, updates your CRM.

Plan and where to find it

Webhooks come with the Pro, Business and Enterprise plans. On other plans the Integrations screen says Webhooks are part of the Pro plan, with a link to the plans.

There are two ways in:

  • From the sidebar: open Integrations (under Account) and click Add webhook, or Add your first webhook if you have none yet.
  • From a page's leads: in Pages, open the page's ⋯ menu and choose Leads. In the Send leads to other apps box, click Add a webhook for this page (or Add another). The same window opens with Form sent on a page ticked and that page chosen.

The Add a webhook window asks for:

  1. Address (https): the address from Zapier, Make or your server, like https://hooks.zapier.com/hooks/catch/….
  2. Name (optional): so you can tell your webhooks apart, like Leads to Google Sheets.
  3. Send these events: tick one or more (see the next section).
  4. Pages or QR codes: All pages and All codes unless you pick one.

Click Add webhook, then Send a test on the webhook's card to check it works. The card also shows its status, its events, its signing secret, when it last sent something, and Recent deliveries. Its ⋯ menu has Edit, New signing secret, Turn off and Remove.

The three events

Each webhook only gets the events you tick for it. Every event arrives in the same wrapper, shown in full in each example below.

Form sent on a pagelead.created

Can be narrowed to one page

Sent when a visitor sends a form on one of your pages: an enquiry, a booking, a sign-up. Inside data.lead:

  • fields: each answer under its label on your form, like fields.Phone. This is the easy one to use in Zapier and Make.
  • answers: the same answers as a list, with each field's fieldId and type. The id doesn't change if you rename the label.
  • page: which page, and form.path: where on it (/ for the main page, or a page inside it like /book).
  • source.qrId: the QR code the visitor scanned to get there, or null if they came another way.

Every answer is text, even numbers and dates (a date looks like 2026-11-02; a tick box is Yes or No). Fields the visitor left empty aren't included.

Example payload
Example: lead.created
{
  "id": "evt_Qm8xT2vLk9PzR4aN",
  "type": "lead.created",
  "created": "2026-10-25T09:41:07.512Z",
  "version": 1,
  "workspaceId": "ws_4f9a1c7e2b8d3a6f0e51",
  "data": {
    "lead": {
      "id": "lead_1842",
      "createdAt": "2026-10-25T09:41:07.498Z",
      "page": {
        "id": "pg_7c3e9a1f4b2d8e",
        "name": "Spice Route Café",
        "url": "https://spiceroute.qrwalrus.page"
      },
      "form": {
        "blockId": "bq4k7m2x",
        "path": "/book"
      },
      "fields": {
        "Name": "Asha Rao",
        "Phone": "+91 98450 12345",
        "Date": "2026-11-02"
      },
      "answers": [
        {
          "fieldId": "fa8k2p1q",
          "label": "Name",
          "type": "text",
          "value": "Asha Rao"
        },
        {
          "fieldId": "fz3m9w4e",
          "label": "Phone",
          "type": "phone",
          "value": "+91 98450 12345"
        },
        {
          "fieldId": "fd6t1y8u",
          "label": "Date",
          "type": "date",
          "value": "2026-11-02"
        }
      ],
      "source": {
        "qrId": "Xk29dPq7LmZa"
      }
    }
  }
}

QR code scannedscan.created

Can be narrowed to one QR code

Sent when one of your dynamic codes is scanned. Each webhook gets at most 120 scans a minute: extra ones in that minute are skipped, and the webhook's card counts them (scan events skipped). The scans themselves still count in QR Walrus as usual.

No IP address and no precise location are sent: only the country (a two-letter code like IN) and city. Also inside data.scan:

  • qr: which code; device: mobile, tablet or desktop; os, browser and language.
  • repeat: true when the same phone scanned this code a moment ago.
  • rule: the smart rule that chose where the scan went, or null; served: what kind of content it opened.
  • referrer and utm: usually null for a camera scan.
Example payload
Example: scan.created
{
  "id": "evt_Lw4pZ8cRt2NkX7vB",
  "type": "scan.created",
  "created": "2026-10-25T12:05:33.871Z",
  "version": 1,
  "workspaceId": "ws_4f9a1c7e2b8d3a6f0e51",
  "data": {
    "scan": {
      "qr": {
        "id": "Xk29dPq7LmZa",
        "name": "Table tent",
        "contentType": "page"
      },
      "scannedAt": "2026-10-25T12:05:33.860Z",
      "repeat": false,
      "device": "mobile",
      "os": "Android",
      "browser": "Chrome",
      "country": "IN",
      "city": "Pune",
      "language": "en",
      "referrer": null,
      "rule": null,
      "served": "page",
      "utm": null
    }
  }
}

Guest photo addedguest_photo.submitted

Can be narrowed to one page

Sent when a visitor adds a photo to a Guest photos block. It waits for your approval: status is pending, and reviewUrl opens the screen where you approve it. The photo only shows on your page after you do.

Inside data.photo you also get the photo's url, its width and height, and uploaderName if the visitor typed a name (otherwise null).

Example payload
Example: guest_photo.submitted
{
  "id": "evt_Gt6yH1mQs9DfJ3wP",
  "type": "guest_photo.submitted",
  "created": "2026-10-25T18:22:41.230Z",
  "version": 1,
  "workspaceId": "ws_4f9a1c7e2b8d3a6f0e51",
  "data": {
    "photo": {
      "id": "photo_311",
      "submittedAt": "2026-10-25T18:22:41.214Z",
      "status": "pending",
      "page": {
        "id": "pg_2b8d5f0a9c1e7d",
        "name": "Riya & Kabir's wedding",
        "url": "https://riyakabir.qrwalrus.page"
      },
      "blockId": "bw7e2r5t",
      "url": "https://ik.imagekit.io/qrwalrus/page-uploads/pg_2b8d5f0a9c1e7d/guest_Lq2wX9.jpg",
      "width": 1600,
      "height": 1200,
      "uploaderName": "Meera",
      "reviewUrl": "https://www.qrwalrus.com/dashboard/pages/pg_2b8d5f0a9c1e7d/photos"
    }
  }
}

The wrapper every event comes in

  • id: the event, like evt_Qm8x…. It's the same for every webhook that gets the event, and stays the same on retries, so use it to skip repeats.
  • type: which event. created: when it happened, in UTC.
  • version: 1. It only changes if a field is ever removed or changes meaning. New fields can appear at any time, so ignore ones you don't use.
  • workspaceId: your workspace. data: the event itself.

Fields can arrive in a different order than in these examples. Read them by name.

The test event

Send a test sends a ping event to that one webhook, whatever events it's set to. Use it to check the address works and to see the wrapper; it shows up in the delivery log too. It has no form fields, so to set up field mapping in Zapier or Make, send one real form as well.

Example: ping
{
  "id": "evt_Hc3nW7sKp1VbY6dE",
  "type": "ping",
  "created": "2026-10-25T09:30:12.004Z",
  "version": 1,
  "workspaceId": "ws_4f9a1c7e2b8d3a6f0e51",
  "data": {
    "endpoint": "wh_Zx81LmQp4TnA",
    "message": "This is a test event from QR Walrus. Your endpoint is set up correctly."
  }
}

Only one page, or one code

When you tick Form sent on a page or Guest photo added, a Pages list appears: keep All pages, or pick Only one page. When you tick QR code scanned, a QR codes list does the same for codes. The card then says One page only or One code only.

The page you pick applies only to form and photo events, and the code only to scans. So one webhook can take one page's leads and every scan, or every lead and one code's scans.

Set up with Zapier

Zapier needs a paid plan for this

Webhooks by Zapier, the Zapier app that receives our events, needs a paid Zapier plan (Professional, from about $19.99 a month). New Zapier accounts get a 14-day trial. If you'd rather not pay, Make's free plan works.

  1. In Zapier, create a new Zap. For the trigger, search for Webhooks by Zapier and choose the event Catch Hook. Click Continue, leaving the optional settings empty.
  2. Zapier shows your webhook URL, starting with https://hooks.zapier.com/hooks/catch/. Copy it.
  3. In QR Walrus, open Integrations → Add webhook, paste it into Address, tick the events and click Add webhook.
  4. Click Send a test on the new webhook. You should see Test delivered (200).
  5. Back in Zapier, click Test trigger. It finds the test event (type ping).
  6. For your form's fields to show up, send one real form on your page yourself (fill in every field), then test the trigger again in Zapier and pick that request.
  7. Add the action (Google Sheets, Gmail, Slack, your CRM…) and map the fields. Form answers are under data.lead.fields, which Zapier shows as names like Data Lead Fields Phone. Then publish the Zap.

Tip: make one webhook per Zap and tick only the event that Zap handles, so a scan never lands in your leads sheet.

Set up with Make

Make's free plan works with webhooks.

  1. In Make, create a new scenario. Click the big + and choose Webhooks → Custom webhook.
  2. Click Create a webhook (or Add), name it (like QR Walrus leads) and save. Make shows its address, starting with https://hook.. Click Copy address to clipboard.
  3. In QR Walrus, open Integrations → Add webhook, paste it into Address, tick the events and click Add webhook.
  4. While Make is waiting for data, click Send a test in QR Walrus. Make says it successfully determined the data structure: it has learned what our events look like.
  5. The test has no form fields. For Make to learn yours, open the webhook module, click Redetermine data structure, then send one real form on your page (fill in every field). Now data.lead.fields and each field in it are ready to map.
  6. Add the next module (Google Sheets, Gmail, Slack, a CRM…), map the fields, save, and switch the scenario on so it runs as soon as data arrives.

Leads into Google Sheets

Every form sent on your page becomes a new row. This uses Make, which is free for this:

  1. In Google Sheets, make a sheet with headings in the first row, like Date, Name, Phone, Message: one per form field you want to keep.
  2. In QR Walrus, open the page's Leads screen and click Add a webhook for this page (or use Integrations → Add webhook and tick only Form sent on a page).
  3. In Make, set up a Custom webhook as in Set up with Make, paste its address into QR Walrus, and send one real form so Make learns your fields.
  4. Add a Google Sheets module after it and choose Add a Row. Connect your Google account, pick the spreadsheet and the sheet, and say the table has headers.
  5. Map each column: Date to data.lead.createdAt, Name to data.lead.fields.Name, Phone to data.lead.fields.Phone, and so on.
  6. Save, switch the scenario on, and send another form to see the row appear.
  • Fields are named after their labels on your form. If you rename a field, map it again.
  • A field the visitor left empty isn't sent, so its cell stays blank.
  • Times are in UTC (they end in Z).
  • Zapier works too, on a paid Zapier plan: Catch Hook as the trigger, then Google Sheets Create Spreadsheet Row.

Your own server

For developers: we send a POST with a JSON body to your https address. Answer with any 2xx status within 5 seconds; we don't read the body of your answer. Do anything slow (sending email, calling other services) after you answer, or put it on a queue.

Headers

  • Content-Type

    application/json

    The body is JSON.

  • User-Agent

    QRWalrus-Webhooks/1.0

    Who is calling.

  • X-QRWalrus-Event

    lead.created

    The event, the same as type in the body.

  • X-QRWalrus-Delivery

    dlv_5821

    This delivery to this address. It stays the same when the delivery is retried or resent.

  • X-QRWalrus-Signature

    t=1792921267,v1=37954c241d9ba3…

    When we signed it (Unix seconds) and the signature: hex HMAC-SHA256 of the timestamp, a dot and the raw body.

Check that it came from us

  1. Read the raw body exactly as it arrived, before any JSON parsing.
  2. Split X-QRWalrus-Signature at the comma into t (a Unix timestamp in seconds) and v1.
  3. Compute HMAC-SHA256 of <t>.<raw body> (the timestamp, a dot, then the body), keyed with the webhook's signing secret, the whole thing including whsec_. Write it as lowercase hex.
  4. Compare it with v1 using a constant-time comparison.
  5. Reject timestamps more than 5 minutes old. Every try is signed afresh, so retries pass this check.
  6. Skip an event whose id you've already handled: a retry after a timeout can arrive twice.
Node.js (Express)
// server.mjs: npm install express, then node server.mjs
// Run it behind HTTPS on your domain: we only send to https:// addresses on port 443.
import crypto from "node:crypto";
import express from "express";

const SECRET = process.env.QRWALRUS_WEBHOOK_SECRET; // the whole whsec_… value
const seen = new Set(); // event ids already handled; keep them in your database in production

function isGenuine(rawBody, header) {
  const parts = {};
  for (const piece of (header || "").split(",")) {
    const i = piece.indexOf("=");
    if (i > 0) parts[piece.slice(0, i).trim()] = piece.slice(i + 1).trim();
  }
  const t = Number(parts.t);
  if (!parts.t || !Number.isInteger(t) || !parts.v1) return false;
  if (Math.abs(Date.now() / 1000 - t) > 300) return false; // more than 5 minutes old
  const expected = crypto.createHmac("sha256", SECRET).update(parts.t + ".").update(rawBody).digest("hex");
  const a = Buffer.from(expected);
  const b = Buffer.from(parts.v1);
  return a.length === b.length && crypto.timingSafeEqual(a, b);
}

function handle(event) {
  if (event.type === "lead.created") console.log("New lead:", event.data.lead.fields);
  else if (event.type === "scan.created") console.log("Scan:", event.data.scan.qr.name, event.data.scan.city);
  else if (event.type === "guest_photo.submitted") console.log("Photo to approve:", event.data.photo.reviewUrl);
  else if (event.type === "ping") console.log("Test event received");
}

const app = express();

// express.raw keeps the exact bytes we signed. Don't put express.json() in front of this route.
app.post("/webhooks/qrwalrus", express.raw({ type: "application/json" }), (req, res) => {
  if (!Buffer.isBuffer(req.body) || !isGenuine(req.body, req.get("X-QRWalrus-Signature"))) {
    return res.status(401).send("Invalid signature");
  }
  const event = JSON.parse(req.body.toString("utf8"));
  if (seen.has(event.id)) return res.sendStatus(200); // a repeat: already handled
  seen.add(event.id);
  res.sendStatus(200); // answer within 5 seconds…
  setImmediate(() => handle(event)); // …then do the slow work
});

app.listen(3000, () => console.log("Listening on port 3000"));
Python (Flask)
# app.py: pip install flask, then flask --app app run --port 5000
# Run it behind HTTPS on your domain: we only send to https:// addresses on port 443.
import hashlib
import hmac
import json
import os
import threading
import time

from flask import Flask, abort, request

app = Flask(__name__)
SECRET = os.environ["QRWALRUS_WEBHOOK_SECRET"].encode()  # the whole whsec_… value
seen = set()  # event ids already handled; keep them in your database in production


def is_genuine(raw_body: bytes, header: str) -> bool:
    parts = {}
    for piece in header.split(","):
        key, sep, value = piece.strip().partition("=")
        if sep:
            parts[key] = value
    t = parts.get("t", "")
    if not t.isdigit() or abs(time.time() - int(t)) > 300:  # more than 5 minutes old
        return False
    expected = hmac.new(SECRET, t.encode() + b"." + raw_body, hashlib.sha256).hexdigest()
    return hmac.compare_digest(expected.encode(), parts.get("v1", "").encode())


def handle(event: dict) -> None:
    if event["type"] == "lead.created":
        print("New lead:", event["data"]["lead"]["fields"])
    elif event["type"] == "scan.created":
        print("Scan:", event["data"]["scan"]["qr"]["name"], event["data"]["scan"]["city"])
    elif event["type"] == "guest_photo.submitted":
        print("Photo to approve:", event["data"]["photo"]["reviewUrl"])
    elif event["type"] == "ping":
        print("Test event received")


@app.post("/webhooks/qrwalrus")
def qrwalrus_webhook():
    raw = request.get_data()  # the exact bytes we signed
    if not is_genuine(raw, request.headers.get("X-QRWalrus-Signature", "")):
        abort(401)
    event = json.loads(raw)
    if event["id"] in seen:  # a repeat: already handled
        return "", 200
    seen.add(event["id"])
    threading.Thread(target=handle, args=(event,)).start()  # answer within 5 seconds, work after
    return "", 200
PHP
<?php
// webhook.php: use this file's https:// address as the webhook's address.
$secret = (string) getenv('QRWALRUS_WEBHOOK_SECRET'); // the whole whsec_… value
$raw = file_get_contents('php://input'); // the exact bytes we signed
$header = $_SERVER['HTTP_X_QRWALRUS_SIGNATURE'] ?? '';

$parts = [];
foreach (explode(',', $header) as $piece) {
    $pair = explode('=', trim($piece), 2);
    if (count($pair) === 2) {
        $parts[$pair[0]] = $pair[1];
    }
}
$t = $parts['t'] ?? '';
$expected = hash_hmac('sha256', $t . '.' . $raw, $secret);

// Reject a bad signature, or one more than 5 minutes old.
if (!ctype_digit($t) || abs(time() - (int) $t) > 300 || !hash_equals($expected, $parts['v1'] ?? '')) {
    http_response_code(401);
    exit('Invalid signature');
}

$event = json_decode($raw, true);
// To skip repeats, save $event['id'] in your database and stop here if it is already there.

switch ($event['type']) {
    case 'lead.created':
        $fields = $event['data']['lead']['fields']; // for example $fields['Name'], $fields['Phone']
        error_log('New lead: ' . json_encode($fields));
        break;
    case 'scan.created':
        error_log('Scan of ' . $event['data']['scan']['qr']['name']);
        break;
    case 'guest_photo.submitted':
        error_log('Photo to approve: ' . $event['data']['photo']['reviewUrl']);
        break;
    case 'ping':
        error_log('Test event received');
        break;
}

http_response_code(200);

Using another framework? Make sure you hash the body before anything parses it. In a Next.js route handler, for example, read it with await request.text().

Check your code with this example

With these values, your code should get exactly this signature (leave out the 5-minute check while you try it):

Secret
whsec_example_only
Body
{"id":"evt_test","type":"ping"}
Header
t=1792921267,v1=37954c241d9ba3f302e51f93c2873ddff36d42416aa48c4939b49305695a6c23
  • X-QRWalrus-Delivery names one delivery to one address. The event id in the body is the one to skip repeats by.
  • Resend in the log sends the same event with the same id. If your receiver skips ids it has seen, only mark an id as seen once you've handled it.

Retries and the delivery log

A 2xx answer means delivered. If a try doesn't go through, we try again on this schedule, 7 tries in all over about 35 hours:

TryWhen
1Right away, as soon as the event happens
21 minute after try 1
35 minutes after try 2
430 minutes after try 3
52 hours after try 4
68 hours after try 5
724 hours after try 6

A retry is never sent early, but it can go out a little later than listed.

Tried again

  • 429 (too many requests)
  • Any 5xx (a server error)
  • No answer within 5 seconds
  • Couldn't connect

Not tried again

  • Any other 4xx, like 400, 401, 404
  • A redirect (3xx): we don't follow redirects, so use the final address
  • An address that is no longer reachable from the internet

After 20 deliveries in a row fail for good, we turn the webhook off so it stops piling up failures. Its card then says: Turned off after 20 deliveries in a row failed. Fix the address, then turn it back on. Deliveries still waiting to be retried stop too, and events that happen while it's off aren't sent.

The status on each webhook's card:

Working
The last delivery got a 2xx answer.
Failing
The last try didn't go through. Retries may still be on their way.
Not used yet
Nothing has been sent yet. Send a test.
Off
You turned it off. Nothing is sent until you turn it on.
Turned off
We turned it off after 20 deliveries in a row failed.
Paused (plan)
Your plan doesn't include webhooks at the moment.

The delivery log

Click Recent deliveries on a webhook to see its latest 50 deliveries. They're kept for 30 days. Each one shows:

  • Delivered, Retrying or Failed, the event, the time, the HTTP status or the error, how many tries it took and, while retrying, when the next try is.
  • Payload: exactly what we sent.
  • Resend: sends it again now, with a fresh set of retries. It appears on delivered and failed rows while the webhook is on.

To bring back a webhook that was turned off: fix the address or your receiver, then choose ⋯ → Turn on. That clears its failure count. Then resend, from the log, any failed deliveries you still need.

Your signing secret

Each webhook has its own signing secret, starting with whsec_. You only need it if you check signatures on your own server; Zapier and Make don't use it.

  • See it: on the webhook's card, next to Signing secret, click the eye to show it, or the copy button to copy it.
  • Make a new one: choose ⋯ → New signing secret → Make new secret. The old secret stops working at once, so update it wherever you check signatures, or our deliveries will look forged there. Deliveries waiting to be retried are signed with the new one.

Keep the secret on your server, never in a web page or an app. If you think someone has seen it, make a new one.

Limits

Webhooks
Up to 10 per workspace.
Address
Starts with https:// on the standard port (443). A domain name, not an IP address. No user name or password in it. At most 2,048 characters.
Reachable
On the public internet. Not localhost, private network addresses (like 192.168.x.x or 10.x.x.x) or names like printer.local.
Time to answer
5 seconds. We don't read what you answer, only the status code.
Redirects
Not followed. Use the final address.
Scan events
Up to 120 a minute per webhook. Extra scans in that minute are skipped and counted.
Tries
7 per delivery, over about 35 hours.
Turned off
After 20 deliveries in a row fail for good.
Delivery log
The latest 50 deliveries per webhook, kept 30 days.

We check the address when you save it and again before every delivery.

If it doesn't work

Zapier says Webhooks by Zapier needs an upgrade

  • It's a premium Zapier app: it needs a paid Zapier plan (Professional, from about $19.99 a month). New accounts get a 14-day trial.
  • Or use Make, whose free plan works.

The test fails with a 4xx answer, like “Answered 404”

  • 404 usually means the address was copied incompletely, or the Zap, scenario or webhook at the other end was deleted. Copy the address again and paste it in with Edit.
  • 401 or 403 means your server refused it. Check your signature code (below). If your address needs a login or an API key, use a signature check instead: we can't add a password or key to the request.
  • 400 means your server didn't accept the body. Open Payload in the log to see what we sent.
  • These aren't retried. Fix the cause, then click Send a test or Resend.

The test fails with a 5xx, “No answer within 5 seconds” or “Couldn't connect”

  • Your receiver is down, slow or erroring. Check its logs. We retry these on the schedule above.
  • Answer first and do slow work afterwards: anything over 5 seconds counts as no answer.
  • Your address needs a valid HTTPS certificate. A self-signed or expired one can't be connected to.

“Redirected (301); use the final address”

  • The address sent us somewhere else, and we don't follow redirects. Open it in a browser, see where it ends up (often www., https or a trailing slash) and use that.

“That address isn't reachable from the internet”

  • The address is on your own computer or network: localhost, an address like 192.168.x.x or 10.x.x.x, a name like printer.local, or a name without a dot.
  • Use a public address. To test a server on your computer, a tunnel such as ngrok or Cloudflare Tunnel gives you a public https address.
  • Other messages when you save: Use a domain name, not an IP address, Use the standard https port (remove :8443 or similar), and Leave the user name and password out of the address.

Signatures don't match

  • Hash the raw body exactly as it arrived. Parsing the JSON and turning it back into text changes it.
  • Use the whole secret, including whsec_, and the secret of this webhook: each one has its own.
  • If you made a new signing secret, update it on your server.
  • Sign the timestamp, a dot and the body, and compare lowercase hex.
  • If old timestamps are rejected, check your server's clock.
  • Try the worked example in the Your own server section.

The same event arrives twice

  • We didn't get your answer within 5 seconds, or the connection dropped, so we tried again. Or someone clicked Resend.
  • Skip events whose id you've already handled, and answer before doing slow work.

“Scan events skipped (over 120 a minute)”

  • A busy code went over 120 scans a minute, so the extra scan events weren't sent to this webhook. This protects your receiver from floods.
  • The scans themselves still count in QR Walrus. Only the webhook for them is skipped.

The webhook was turned off

  • We turn a webhook off after 20 deliveries in a row fail for good. Open Recent deliveries to see the errors.
  • Fix the address or your receiver, then ⋯ → Turn on. Resend the failed deliveries you need from the log (within 30 days).
  • Events from while it was off weren't sent and can't be resent.

Delivered, but nothing happens in Zapier or Make

  • Publish the Zap, or switch the Make scenario on. Until then they may accept the event without acting on it.
  • Check the webhook has the right events ticked, and isn't narrowed to a different page or code.
  • A change you just made can take up to a minute to apply.

Zapier or Make doesn't show my form's fields

  • The test event has no form fields. Send one real form on your page, then test the trigger again (Zapier) or redetermine the data structure (Make).
  • Fill in every field in that form: empty fields aren't sent.
  • If you renamed a field on your form, it arrives under its new name. Map it again.

Questions about webhooks

Do I need to know how to code?

No. With Zapier or Make you connect QR Walrus to other apps by clicking: copy an address from them, paste it into QR Walrus and pick which fields go where. You only need code to receive events on your own server.

Can I use the free Zapier plan?

No. Webhooks by Zapier, the Zapier app that receives our events, needs a paid Zapier plan (Professional, from about $19.99 a month; new accounts get a 14-day trial). Make's free plan works with webhooks, so it's the cheapest way to start.

How quickly do events arrive?

We send each event as soon as it happens, so it usually reaches Zapier, Make or your server within seconds. What happens after that depends on that app.

What if my app or server is down?

We try again up to 6 more times over about 35 hours. If every try fails, the delivery is marked Failed in the webhook's log, where you can see exactly what was sent and resend it for 30 days.

Can different pages send their leads to different places?

Yes. Add one webhook per destination and narrow each one to a page (or, for scans, to one QR code). You can have up to 10 webhooks.

Do you send my visitors' IP addresses or exact location?

No. A scan event has the country and city, the kind of device, the operating system, the browser and the language, but never an IP address or a precise location. A form event has what the visitor typed into your form.

Are guest photos sent before I approve them?

The event is sent as soon as a visitor adds a photo, with the status pending and a link to the screen where you approve it. The photo itself only shows on your page after you approve it.

What happens to my webhooks if my plan changes?

Webhooks come with the Pro, Business and Enterprise plans. On another plan your webhooks are kept but paused, and nothing new is sent. They start again when you're back on a plan that includes them.

Still stuck? Contact us with a screenshot of the webhook's delivery log (never your signing secret), and we'll help you find the snag.