Developers
Local test examples
Run these scripts on your laptop to receive test webhooks and call the REST API. No extra dependencies beyond Node.js 18+.
Before you start
Open Dashboard → API Keys, create an API key, save webhook URLhttp://127.0.0.1:4242/webhook, and copy your whsec_… signing secret. See the API reference for the full workflow.1. Project setup
Create a folder anywhere on your machine.
mkdir prostnow-api-test && cd prostnow-api-test.env
# From Dashboard → API Keys
PROSTNOW_API_KEY=pn_live_your_key_here
PROSTNOW_WEBHOOK_SECRET=whsec_your_signing_secret_here
# Your Prostnow site (local dev or production)
PROSTNOW_BASE_URL=https://www.prostnow.com
# Local webhook listener port
WEBHOOK_PORT=4242Load env vars in your shell: export $(grep -v '^#' .env | xargs) (macOS/Linux).
2. Local webhook listener
Receives POSTs from Prostnow, verifies the signature, and prints the payload.
Save as webhook-server.mjs and run with node webhook-server.mjs.
// webhook-server.mjs — Node 18+ (no npm install required)
import crypto from "crypto";
import http from "http";
const PORT = Number(process.env.WEBHOOK_PORT || 4242);
const SECRET = process.env.PROSTNOW_WEBHOOK_SECRET;
if (!SECRET) {
console.error("Set PROSTNOW_WEBHOOK_SECRET in your environment");
process.exit(1);
}
function verifySignature(rawBody, signatureHeader, secret) {
const parts = Object.fromEntries(
signatureHeader.split(",").map((p) => p.trim().split("="))
);
const timestamp = parts.t;
const expected = parts.v1;
if (!timestamp || !expected) return false;
const signed = `${timestamp}.${rawBody}`;
const digest = crypto.createHmac("sha256", secret).update(signed).digest("hex");
try {
return crypto.timingSafeEqual(
Buffer.from(digest, "hex"),
Buffer.from(expected, "hex")
);
} catch {
return false;
}
}
const server = http.createServer((req, res) => {
if (req.method === "GET" && req.url === "/health") {
res.writeHead(200, { "Content-Type": "text/plain" });
res.end("ok");
return;
}
if (req.method !== "POST" || req.url !== "/webhook") {
res.writeHead(404).end();
return;
}
const chunks = [];
req.on("data", (c) => chunks.push(c));
req.on("end", () => {
const rawBody = Buffer.concat(chunks).toString("utf8");
const sig = req.headers["x-prostnow-signature"];
const ts = req.headers["x-prostnow-timestamp"];
if (typeof sig !== "string" || !verifySignature(rawBody, sig, SECRET)) {
console.error("✗ Invalid signature");
res.writeHead(401).end("invalid signature");
return;
}
let payload;
try {
payload = JSON.parse(rawBody);
} catch {
res.writeHead(400).end("invalid json");
return;
}
const label = payload.test ? "TEST" : "LIVE";
console.log(`\n✓ [${label}] post.sent received`);
console.log(" timestamp:", ts);
console.log(" post id:", payload.post?.id);
console.log(" sent:", payload.post?.sent);
if (payload.test) {
console.log(" (dashboard test event — safe to ignore in prod logs)\n");
}
res.writeHead(200, { "Content-Type": "application/json" });
res.end(JSON.stringify({ received: true }));
});
});
server.listen(PORT, "127.0.0.1", () => {
console.log(`Webhook listener → http://127.0.0.1:${PORT}/webhook`);
console.log("Health check → http://127.0.0.1:" + PORT + "/health");
console.log("\nIn Dashboard → API Keys, set endpoint URL to:");
console.log(` http://127.0.0.1:${PORT}/webhook\n`);
});- 1Start the server: node webhook-server.mjs
- 2In Dashboard → API Keys, set endpoint to http://127.0.0.1:4242/webhook and Save.
- 3Click Send test webhook — your terminal should print ✓ [TEST] post.sent received.
3. Test the REST API
Confirm your API key works with curl or a small script.
List recent posts
# macOS / Linux — load PROSTNOW_API_KEY and PROSTNOW_BASE_URL first
curl -s \
-H "Authorization: Bearer $PROSTNOW_API_KEY" \
"$PROSTNOW_BASE_URL/api/v1/posts?limit=5" | jq .Get one post by id
POST_ID="paste_post_id_from_dashboard_or_list_response"
curl -s \
-H "Authorization: Bearer $PROSTNOW_API_KEY" \
"$PROSTNOW_BASE_URL/api/v1/posts/$POST_ID" | jq .Optional: test-rest.mjs
// test-rest.mjs
const base = process.env.PROSTNOW_BASE_URL?.replace(/\/$/, "");
const key = process.env.PROSTNOW_API_KEY;
if (!base || !key) {
console.error("Set PROSTNOW_BASE_URL and PROSTNOW_API_KEY");
process.exit(1);
}
const res = await fetch(`${base}/api/v1/posts?limit=5`, {
headers: { Authorization: `Bearer ${key}` },
});
console.log("HTTP", res.status);
console.log(await res.json());node test-rest.mjs4. End-to-end on your laptop
Combine webhook listener + REST in one session.
- Terminal 1Run
node webhook-server.mjsand leave it running. - Terminal 2Run
node test-rest.mjsto confirm REST auth. - DashboardSend test webhook, then publish a real post and watch for a
[LIVE]event (notest: trueflag).
5. Using a tunnel (optional)
When you need a public https URL instead of localhost.
Tools like ngrok or Cloudflare Tunnel can expose your local listener:
# Example with ngrok (after installing ngrok)
ngrok http 4242
# Copy the https URL, e.g. https://abc123.ngrok-free.app/webhook
# Paste it in Dashboard → API Keys → Endpoint URL → Save → Send test webhookProduction webhooks require
https://. Local testing accepts http://127.0.0.1 and http://localhost only.Troubleshooting
- Invalid signature — use the raw request body for HMAC, not re-parsed JSON. Confirm
PROSTNOW_WEBHOOK_SECRETmatches the dashboard. - 401 on REST — key must start with
pn_live_and be sent asAuthorization: Bearer …. - No webhook received — server must listen on
127.0.0.1:4242, URL in dashboard must match exactly, and return HTTP 2xx. - Connection refused — start
webhook-server.mjsbefore clicking Send test webhook.