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
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
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
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
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
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
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)
| Type | What the documentation says | Good for receiving webhooks? |
|---|---|---|
| Reserved VM | Consistent resources for an app that runs continuously | Yes, the natural fit |
| Autoscale | Adjusts resources to usage | Can work; test the start-up time |
| Static | Sites that do not change based on user input | No: it does not run a server |
| Scheduled | Runs at times you choose | Not 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.
