Zeng Book
ProductIndustriesIntegrationsPricingResources
Sign inContact salesTry it free
Introduction
  • Overview
  • Quickstart
API reference
  • Authentication
  • Errors
  • Rate limits
  • Pagination
  • Interactive explorer
Resources
  • Organization
  • Leads
  • Clients
  • Projects
  • Quotations
  • Invoices
Integrations
  • Overview
  • Webhooks
  • Public portal
  • Zapier
  • Make / n8n recipes
  • Xero (coming soon)

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.

Status: app in development
The first-party Zapier app is in active development and targeted for Q2 2026. The HTTP endpoints documented here are live — you can build a custom Zapier private app against them today.

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.

GET/api/v1/me

Auth-test endpoint. Returns the org profile if the key is valid.

Zapier auth test config
{
  "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

POST/api/v1/webhook-subscriptions

Register a webhook target URL with one or more events. Returns an endpoint id, the events array, and a signing secret.

curl
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"
  }'
201 Created
{
  "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"
}
The secret is returned once
Capture secret at creation time. It is never returned again. Use it to verify HMAC signatures on incoming deliveries — see webhooks: verifying signatures.

Unsubscribe

DELETE/api/v1/webhook-subscriptions/{id}

Remove a subscription. 404 if the id does not belong to the authenticated key's org.

curl
curl -X DELETE https://www.zengbook.com/api/v1/webhook-subscriptions/we_01HX... \
  -H "Authorization: Bearer zb_live_..."

List (optional)

GET/api/v1/webhook-subscriptions

List 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:

triggers/invoice_paid.js
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

POST/api/v1/clients

Create a new client in the authenticated org. Returns the created client.

curl
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

POST/api/v1/projects

Create a new project. Status defaults to LEAD. If clientId is provided, it must belong to the same org.

curl
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):

  1. Sign in to the Zapier developer platform and create a new integration.
  2. Configure authentication as "API key" with the auth test pointed at /api/v1/me.
  3. Define triggers as REST Hooks subscriptions to the events you care about (see the event reference).
  4. Define actions as POST calls to /api/v1/clients and /api/v1/projects.
  5. 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.