Webhook setup
Configure StaticPagesApi to send exported HTML to your site and verify signed deliveries.
Create a receiving endpoint
Your site needs a public HTTPS endpoint that accepts POST requests. StaticPagesApi sends the exported page HTML as the raw request body with Content-Type text/html; charset=utf-8.
post "/page-builder-webhook" do
html = request.raw_post
# Save html to a static file, deploy it, or hand it to your publishing pipeline.
head :ok
end
Save the webhook in StaticPagesApi
Open Webhooks, create a saved webhook, and enter the full HTTPS URL for your receiving endpoint. Then open a page's Webhooks screen and attach that saved webhook to the page.
- A saved webhook can be reused across multiple pages.
- Each page tracks when it last sent each webhook.
- Webhook delivery runs when a page update publishes static HTML for attached webhooks.
Copy the signing secret
Open the webhook detail page and copy its signing secret. Store it on your receiving site as a server-side secret such as STATIC_PAGES_API_WEBHOOK_SECRET. If you regenerate the signing secret, update the receiver before relying on new deliveries.
STATIC_PAGES_API_WEBHOOK_SECRET=whsec_your_secret_here
Verify delivery signatures
Every delivery includes X-Static-Pages-Api-Timestamp and X-Static-Pages-Api-Signature. Reject old timestamps, compute HMAC-SHA256 over timestamp.raw_body, and compare it to the v1 signature using a constant-time comparison.
timestamp = request.headers.fetch("X-Static-Pages-Api-Timestamp")
signature = request.headers.fetch("X-Static-Pages-Api-Signature").delete_prefix("v1=")
body = request.raw_post
age = (Time.now.to_i - timestamp.to_i).abs
halt 401 if age > 300
expected = OpenSSL::HMAC.hexdigest(
"SHA256",
ENV.fetch("STATIC_PAGES_API_WEBHOOK_SECRET"),
"#{timestamp}.#{body}"
)
halt 401 unless Rack::Utils.secure_compare(expected, signature)
Read delivery metadata
The request also includes page metadata headers so your receiver can decide where to write or deploy the incoming HTML.
- X-Static-Pages-Api-Page-Id is the StaticPagesApi page id.
- X-Static-Pages-Api-Page-Slug is the page slug.
- The request body is the complete exported HTML document.
Minimal Node receiver
The important detail is to verify against the exact raw request body before parsing or transforming it.
import crypto from "crypto";
import express from "express";
const app = express();
app.post(
"/page-builder-webhook",
express.raw({ type: "text/html" }),
(req, res) => {
const timestamp = req.header("X-Static-Pages-Api-Timestamp");
const signature = req.header("X-Static-Pages-Api-Signature")?.replace(/^v1=/, "");
const body = req.body.toString("utf8");
if (Math.abs(Date.now() / 1000 - Number(timestamp)) > 300) {
return res.sendStatus(401);
}
const expected = crypto
.createHmac("sha256", process.env.STATIC_PAGES_API_WEBHOOK_SECRET)
.update(`${timestamp}.${body}`)
.digest("hex");
if (!crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(signature))) {
return res.sendStatus(401);
}
// Save or deploy body here.
res.sendStatus(200);
}
);
© 2026 StaticPagesApi. All rights reserved.
Documentation, examples, prompts, templates, and API guidance are provided for authorized use with StaticPagesApi.