Authentication
Every request authenticates with a per-business API key sent in the X-API-Key header. Keys are issued per account during setup — there is no self-serve key rotation yet, so ask support and a person will mint or roll one for you.
curl https://revenuepack.com/api/zapier/me \
-H "X-API-Key: YOUR_API_KEY"A missing, malformed, or unknown key returns 401 {"error":"unauthorized"}. Keys shorter than 16 characters are rejected without a database lookup. An account with no key provisioned simply cannot authenticate — that is the same 401.
The lead webhook
The primary integration path. When RevenuePack captures a lead — from an answered call, a missed-call recovery, or a web form — we POST the record to a URL you nominate. Enable it by giving support your endpoint; we store the URL and a shared secret against your business.
Delivery rides the same queue as our other notification channels, so transient failures are retried and permanent ones are recorded. We POST to HTTPS destinations only (plain HTTP is accepted solely on loopback, for local testing).
Headers
X-RevenuePack-Event— alwayslead.capturedtoday.X-RevenuePack-Timestamp— ISO 8601, and the first half of the signed string.X-RevenuePack-Signature—sha256=<hex>, present whenever a secret is configured.
Payload
{
"id": "lead_9f3c2ab1",
"event": "lead.captured",
"business": {
"id": "biz_1a2b3c",
"slug": "acme-plumbing",
"name": "Acme Plumbing"
},
"lead": {
"id": "lead_9f3c2ab1",
"customer_name": "Jordan R.",
"customer_phone": "+13175550142",
"service_needed": "Water heater leaking near the base",
"service_address": "142 Maple St, Indianapolis IN",
"urgency": "soon",
"estimated_value_cents": null,
"captured_at": "2026-07-25T02:41:19.442Z"
},
"call": {
"id": "call_77de12",
"started_at": "2026-07-25T02:39:04.001Z",
"duration_ms": 96412,
"summary": "Caller's water heater is leaking; wants someone out tomorrow morning."
}
}call is null for leads that did not come from a phone call — a web-form submission, for example. estimated_value_cents is a planning estimate or null; it is never a claim of earned revenue.
Verifying the signature
The signature is HMAC-SHA256 over the exact string {timestamp}.{rawBody}, keyed with your webhook secret. Verify against the raw request body before parsing it, and compare in constant time.
import crypto from "node:crypto";
// rawBody must be the exact bytes we POSTed — parse JSON only after verifying.
function verify(rawBody, headers, secret) {
const timestamp = headers["x-revenuepack-timestamp"];
const received = headers["x-revenuepack-signature"]; // "sha256=<hex>"
const expected = "sha256=" + crypto
.createHmac("sha256", secret)
.update(`${timestamp}.${rawBody}`, "utf8")
.digest("hex");
const a = Buffer.from(received || "", "utf8");
const b = Buffer.from(expected, "utf8");
return a.length === b.length && crypto.timingSafeEqual(a, b);
}Zapier endpoints
These power our Zapier integration’s New Lead trigger. They are documented because Zapier requires it and because you may want to drive them directly — a REST hook is a perfectly good integration primitive on its own.
GET /api/zapier/me
Returns the business the key belongs to. Zapier uses it as the connection test and to label the connection.
{ "business": "acme-plumbing", "name": "Acme Plumbing" }POST /api/zapier/hooks
Subscribes a URL to future lead.captured events. Body: { "url": "https://hooks.zapier.com/..." }. Returns the created subscription, including its id.
DELETE /api/zapier/hooks/{id}
Unsubscribes. Deleting a subscription that does not belong to your key is treated as not found, never as success.
GET /api/zapier/leads
Returns your three most recent leads, newest first, in exactly the shape a hook delivery uses — so sample data and live data can never drift apart. Each item carries a top-level id, which Zapier uses as its deduplication key.
Hosted intake endpoint
POST /api/intake/{slug}
Accepts a contact-form submission and runs it through the same pipeline as a captured call — owner email, push alert, signed webhook, and the portal ledger. Accepts JSON or standard form encoding, so you can point an ordinary HTML <form> straight at it. We also host a ready-made form for you at /f/{slug}.
The endpoint carries a honeypot field. Submissions that fill it receive a normal 200 and create nothing at all — bots get no signal that they were caught.
Status codes
200— success.400— the body failed validation. The response names the problem.401— missing, malformed or unknown API key.404— unknown business slug, or a subscription that is not yours.410— the resource existed and has expired (demo links, for example).
Rate limits and stability
There is no published rate limit today; these endpoints serve one business each and see human-scale traffic. If we introduce one, it will appear here first with notice. Fields are additive — we may add keys to a payload, so parse defensively and ignore what you do not recognise. We will not remove or rename an existing field on 1.x without a new integration version.
Getting a key
API keys and webhook destinations are provisioned by a person during setup, not self-serve. Open a support request and say what you are building and where you want the data to land.
Related: using RevenuePack with your scheduler covers the non-technical version — which platforms connect, and how.