Integrations
Zapier
A complete spec for the official Zapier app — the same one we're publishing. Useful if you want to build a custom internal Zapier app pointed at your own Zeng Book account, or if you're contributing to the first-party app.
Architecture overview
The Zapier app uses the REST Hooks trigger pattern. When a Zap is turned on, Zapier calls your subscribe endpoint to register a target URL. Your service POSTs to that URL whenever an event fires. When the Zap is turned off, Zapier calls the unsubscribe endpoint to clean up.
For Zeng Book, the "your service" in that flow is us, and the "target URL" is the Zapier-provided hook URL. The Zapier app stores a Zeng Book API key per authenticated user and uses it for every call.
Authentication
The Zapier app uses API key auth. On first connection, Zapier prompts the user for their zb_live_… key. Zapier then validates the key by calling GET /api/v1/me.
/api/v1/meAuth-test endpoint. Returns the org profile if the key is valid.
{
"type": "custom",
"fields": [
{ "key": "apiKey", "label": "Zeng Book API key", "type": "password", "required": true }
],
"test": {
"url": "https://www.zengbook.com/api/v1/me",
"method": "GET",
"headers": { "Authorization": "Bearer {{bundle.authData.apiKey}}" }
}
}Triggers (REST Hooks)
Each Zapier trigger registers a subscription scoped to a specific event type, then receives event payloads via webhook.
Subscribe
/api/v1/webhook-subscriptionsRegister a webhook target URL with one or more events. Returns an endpoint id, the events array, and a signing secret.
curl -X POST https://www.zengbook.com/api/v1/webhook-subscriptions \
-H "Authorization: Bearer zb_live_..." \
-H "Content-Type: application/json" \
-d '{
"url": "https://hooks.zapier.com/hooks/standard/12345/abcde/",
"events": ["invoice.paid"],
"description": "Zapier — when invoice paid"
}'{
"id": "we_01HX...",
"url": "https://hooks.zapier.com/hooks/standard/12345/abcde/",
"events": ["invoice.paid"],
"description": "Zapier — when invoice paid",
"active": true,
"secret": "a1b2c3d4...",
"createdAt": "2026-05-15T03:14:22.412Z"
}secret at creation time. It is never returned again. Use it to verify HMAC signatures on incoming deliveries — see webhooks: verifying signatures.Unsubscribe
/api/v1/webhook-subscriptions/{id}Remove a subscription. 404 if the id does not belong to the authenticated key's org.
curl -X DELETE https://www.zengbook.com/api/v1/webhook-subscriptions/we_01HX... \
-H "Authorization: Bearer zb_live_..."List (optional)
/api/v1/webhook-subscriptionsList all webhook subscriptions for the authenticated key's org. Useful for cleanup; not required by Zapier.
Per-trigger config (Zapier)
Each Zapier trigger (e.g. "New Paid Invoice") is a thin wrapper that subscribes to one event and exposes its payload:
module.exports = {
key: "invoice_paid",
noun: "Invoice",
display: {
label: "Invoice Paid",
description: "Fires when an invoice is marked paid.",
},
operation: {
type: "hook",
performSubscribe: async (z, bundle) => {
const res = await z.request({
url: "https://www.zengbook.com/api/v1/webhook-subscriptions",
method: "POST",
body: { url: bundle.targetUrl, events: ["invoice.paid"] },
})
return res.data // { id, url, events, secret, ... }
},
performUnsubscribe: async (z, bundle) => {
await z.request({
url: `https://www.zengbook.com/api/v1/webhook-subscriptions/${bundle.subscribeData.id}`,
method: "DELETE",
})
},
perform: (z, bundle) => [bundle.cleanedRequest.data.object],
sample: { /* canonical Invoice example */ },
},
}Actions
Actions let a Zap create records in Zeng Book. Each action is a one-to-one wrapper around a POST endpoint.
Create client
/api/v1/clientsCreate a new client in the authenticated org. Returns the created client.
curl -X POST https://www.zengbook.com/api/v1/clients \
-H "Authorization: Bearer zb_live_..." \
-H "Content-Type: application/json" \
-d '{
"name": "Tan Renovations Pte Ltd",
"email": "[email protected]",
"phone": "+65 9123 4567"
}'Required: name. Optional: email, phone, address, notes.
Create project
/api/v1/projectsCreate a new project. Status defaults to LEAD. If clientId is provided, it must belong to the same org.
curl -X POST https://www.zengbook.com/api/v1/projects \
-H "Authorization: Bearer zb_live_..." \
-H "Content-Type: application/json" \
-d '{
"name": "Tampines BTO Reno",
"clientId": "cli_01HX...",
"status": "QUOTED",
"budget": 65000,
"startDate": "2026-06-01T00:00:00.000Z"
}'Roadmap
POST /api/v1/quotations— create a quotation with line items. Q3 2026.POST /api/v1/invoices— create an invoice (or generate from an existing quotation). Q3 2026.POST /api/v1/payments— log a payment against an invoice. Q3 2026.
Rate limits & retry
All endpoints share the same rate-limit budget as the rest of the API — 100 requests per minute per key. See rate limits for headers and recommended backoff. Zapier's default retry behaviour on 429 is sufficient.
For event deliveries, retries follow the standard Zeng Book policy: [5s, 30s, 5m, 30m, 2h], 5 attempts, 10s timeout per attempt. See webhooks: retries.
SSRF guard on subscription URLs
The url field on subscribe is validated against the same blocklist as in-app webhook endpoints: loopback addresses, RFC1918 private ranges, and link-local addresses are all rejected. Zapier's hook URLs (hooks.zapier.com/hooks/...) pass these checks.
Publishing your own private app
If you want to use these endpoints with a custom Zapier private app (for example, an internal automation specific to your firm):
- Sign in to the Zapier developer platform and create a new integration.
- Configure authentication as "API key" with the auth test pointed at
/api/v1/me. - Define triggers as REST Hooks subscriptions to the events you care about (see the event reference).
- Define actions as POST calls to
/api/v1/clientsand/api/v1/projects. - Use the "private" share link from the Zapier developer platform to install in your own Zapier account without going through the public app-store review.