Coding environment and hosting

WhatsApp in Replit: a webhook receiver with signature, Secrets and Reserved VM

Replit is a browser-based development environment with hosting. Its documentation (read on 2026-11-11) explains that each Secret becomes an environment variable of the project and that publishing can be Autoscale, Static, Reserved VM or Scheduled. For ZapZap, the strength is running a server that receives webhooks and answers messages.

Updated on

Why connect

Replit is good for the side that frontend tools do not cover: an always-available server that receives each ZapZap event, checks the signature, avoids processing the same event twice and triggers your logic or an AI model. ZapZap connects numbers through an unofficial channel: WhatsApp may limit or block any number, so pacing, consent and warm-up matter. It costs R$15 per active number per month, prepaid, with no per-message fee, and you start with a welcome credit when you verify your phone, subject to the current campaign.

Step by step

  1. 1

    Create the project and register the Secrets

    In the project editor, open Tools > Secrets (under Setup), click New Secret and create ZAPZAP_API_KEY and ZAPZAP_API_SECRET (from Settings > API at app.zapzapapi.com). They are available as environment variables. The documentation warns that people with access to the project, and collaborators, can see the values: do not invite anyone who should not.

  2. 2

    Start the webhook receiver

    Paste the example server into server.mjs and run it. It reads the raw body, validates X-ZapZap-Signature (t=<unix>,v1=<hmac_hex>, HMAC-SHA256 over t + dot + body, 5-minute tolerance), answers 200 fast and stores the delivery_id to ignore repeats.

  3. 3

    Choose the publishing type

    For a receiver that must always be on, Replit's documentation describes Reserved VM as consistent resources for an app that runs continuously. Autoscale adjusts resources to usage: check that your event volume tolerates the start-up time.

  4. 4

    Register the webhook on the instance

    Call PUT https://api.zapzapapi.com/api/v1/instances/{id}/webhook with webhook_url (HTTPS) and events (for example, messages). The response carries the webhook_secret (whsec_...): save it as the Secret ZAPZAP_WEBHOOK_SECRET. Test with POST /instances/{id}/webhook/test.

  5. 5

    Reply through the API

    To reply, call POST /{instanceId}/send/text with number and text after you have answered 200 to the webhook. The envelope carries delivery_id, event_id, instance_id, instance_name, event and data.

  6. 6

    Debug deliveries

    GET /instances/{id}/webhook/deliveries shows recent deliveries. If your server fails, ZapZap retries with growing intervals, which is why deduplication by delivery_id matters.

Example

// server.mjs: receives the ZapZap webhook, checks the signature and ignores repeated deliveries.
// Replit Secrets: ZAPZAP_WEBHOOK_SECRET (the whsec_ value returned when you set the webhook)
import { createServer } from "node:http";
import { createHmac, timingSafeEqual } from "node:crypto";

const seen = new Set();

function valid(raw, header) {
  const p = Object.fromEntries(String(header || "").split(",").map((kv) => kv.split("=")));
  if (!p.t || !p.v1 || Math.abs(Date.now() / 1000 - Number(p.t)) > 300) return false;
  const mac = createHmac("sha256", process.env.ZAPZAP_WEBHOOK_SECRET).update(p.t + "." + raw).digest("hex");
  return mac.length === p.v1.length && timingSafeEqual(Buffer.from(mac), Buffer.from(p.v1));
}

createServer((req, res) => {
  let raw = "";
  req.on("data", (c) => (raw += c));
  req.on("end", () => {
    if (req.method !== "POST" || !valid(raw, req.headers["x-zapzap-signature"])) {
      res.writeHead(401).end();
      return;
    }
    const env = JSON.parse(raw);
    res.writeHead(200).end("ok"); // answer fast, work afterwards
    if (seen.has(env.delivery_id)) return;
    seen.add(env.delivery_id);
    console.log("event", env.event, "delivery", env.delivery_id);
  });
}).listen(process.env.PORT || 3000, "0.0.0.0");

Illustrative. The exact fields and endpoints are in the documentation.

Replit publishing types, according to the documentation (2026-11-11)

TypeWhat the documentation saysGood for receiving webhooks?
Reserved VMConsistent resources for an app that runs continuouslyYes, the natural fit
AutoscaleAdjusts resources to usageCan work; test the start-up time
StaticSites that do not change based on user inputNo: it does not run a server
ScheduledRuns at times you chooseNot for webhooks; good for routines

Frequently asked questions

How do Replit Secrets reach the code?

As environment variables, read with process.env in Node.js. The documentation read on 2026-11-11 also warns that collaborators with access can see the values.

Do I need to validate the signature?

Yes, if the URL is public. Without it, anyone can fake events. Validation uses the webhook_secret returned when you set the webhook.

Why can the same event arrive twice?

If your server does not answer with success, ZapZap retries. Store the delivery_id and ignore repeats.

Is Replit good for mass campaigns?

The environment works, but ZapZap is an unofficial solution and mass sends and identical messages in a row raise the risk of a block. Send only to people who have a relationship with you.

What does ZapZap cost?

It costs R$15 per active number per month, prepaid, with no per-message fee, and you start with a welcome credit when you verify your phone, subject to the current campaign.

Start now

Create your account, connect a number in minutes and test the API with warm-up and anti-ban. R$15 per active number/month, prepaid, no lock-in.