# Native Zapier / Make Integration

**Date:** 2026-06-01
**Status:** Draft
**Spec:** 176
**Tier:** Business+
**Depends on:** `white-label-api`, `tenant-public-api`, `settings-module`, `oauth-authorization-code`, `marketing-leads-pipeline`, `foundation-auth-rbac`
**Referenced by:** `white-label-api`

---

## Overview

Native Zync integrations on the Zapier and Make (formerly Integromat) platforms, enabling no-code automation for Business+ tenants. This is distinct from the raw webhook API (spec 27) — these are official platform listings with structured triggers and actions, OAuth authentication, and curated event schemas designed for non-technical users.

---

## Zapier Integration

### Platform listing

App name: **Zync** | Category: Business, Finance, Productivity

Listed on `zapier.com/apps/zync` (requires Zapier developer account and public listing review).

### Authentication: OAuth 2.0

Zapier connects to Zync via OAuth 2.0 Authorization Code flow (spec 177 — `oauth-authorization-code`). Scope requested: `read:invoices write:invoices read:customers read:leads write:leads read:time`.

### Triggers (Zapier "When this happens...")

| Trigger | Zync event | Payload fields |
|---------|-----------|----------------|
| New invoice created | `invoice.created` (draft) | invoiceId, invoiceNumber (null at draft), customer, amount, currency |
| Invoice sent | `invoice.sent` | invoiceId, proformaNumber, customer, amount, dueDate |
| Invoice paid | `invoice.paid` | invoiceId, invoiceNumber, customer, amountPaid, paidAt |
| Invoice overdue | `invoice.overdue` | invoiceId, invoiceNumber, customer, amount, daysPastDue |
| New lead | `lead.created` | leadId, name, email, phone, source, stage |
| Lead converted | `lead.converted` | leadId, customerId, customerName |
| New support ticket | `ticket.created` | ticketId, subject, customerName, priority |
| Time entry approved | `time_entry_approved` | timeEntryId, userId, projectId, hours, date |

### Actions (Zapier "Do this...")

| Action | API call | Fields |
|--------|---------|--------|
| Create customer | `POST /api/v1/customers` | name, email, phone, company |
| Create lead | `POST /api/v1/leads` | name, email, phone, source |
| Update lead stage | `PATCH /api/v1/leads/:id` | stage |
| Create invoice | `POST /api/v1/invoices` | customer_id, lines, due_date |
| Send invoice | `POST /api/v1/invoices/:id/send` | — |
| Add time entry | `POST /api/v1/time` | project_id, duration_minutes, description, date |
| Create task | `POST /api/v1/tasks` | title, project_id, assignee_id, due_date |

### Zapier implementation

Zapier apps are defined as YAML/JSON bundles in the Zapier CLI. Key files:

```
packages/zapier/
├── index.js              — app definition
├── authentication.js     — OAuth 2.0 config (client_id, redirect URL, token exchange)
├── triggers/
│   ├── invoice-paid.js
│   ├── new-lead.js
│   └── ...
├── creates/
│   ├── create-customer.js
│   ├── create-invoice.js
│   └── ...
└── searches/
    ├── find-customer.js  — search customers by email
    └── find-project.js
```

Zapier delivers events via polling (not webhooks) for standard-tier apps. REST hooks (push-based) available for Premium-tier apps — Zync uses REST hooks where available to reduce latency.

```ts
// packages/zapier/triggers/invoice-paid.js (REST hook)
subscribe: async (z, bundle) => {
  // Called when user activates the trigger in Zapier
  await z.request({
    method: 'POST',
    url: 'https://api.zync.is/v1/webhooks',
    body: { event: 'invoice.paid', target_url: bundle.targetUrl, name: 'Zapier: invoice paid' }
  })
},
perform: async (z, bundle) => {
  // Called when Zync delivers a webhook event to Zapier
  return [bundle.cleanedRequest.body]
},
```

---

## Make (Integromat) Integration

### Platform listing

App name: **Zync** on Make marketplace (`make.com/en/integrations/zync`).

### Authentication

OAuth 2.0, same flow as Zapier (spec 177).

### Modules (Make terminology for triggers/actions)

**Triggers (Watch):**
- Watch Invoices — emits on `invoice.created` / `invoice.sent` / `invoice.paid`
- Watch Leads — emits on `lead.created` / `lead.stage_updated`
- Watch Tickets — emits on `ticket.created` / `ticket.replied`

**Actions (instant):**
- Create Customer
- Create Invoice
- Create Lead
- Update Lead
- Create Task
- Log Time Entry
- Send Invoice

**Searches:**
- Get Customer by Email
- Get Invoice by Number
- Get Project by Name

Make modules are defined as JSON schemas + Node.js handler functions in the Make custom app SDK.

---

## OAuth App Registration

Both Zapier and Make use the same OAuth 2.0 application. Registered in Zync's admin settings:

```sql
-- OAuth clients table (owned by spec 177)
-- Values for Zapier:
INSERT INTO oauth_clients (client_id, client_secret_hash, name, redirect_uris, scopes, is_first_party)
VALUES (
  'zapier_zync',
  SHA256('secret'),
  'Zapier',
  '["https://zapier.com/dashboard/auth/oauth/return/App1234CLIAPI/"]',
  '["read:invoices", "write:invoices", "read:customers", "write:customers", "read:leads", "write:leads", "read:time", "write:time"]',
  false  -- third-party
);
-- Separate client for Make:
INSERT INTO oauth_clients (...) VALUES ('make_zync', ...);
```

---

## Settings UI

`/settings/integrations` → "Zapier" card + "Make" card:

```
┌────────────────────────────────────────────────────────────┐
│  Zapier                                     [Connect ▾]    │
│  Automate Zync with 5,000+ apps.                           │
│  Last sync: Jun 01, 14:32                                  │
│  Active Zaps: 3                                            │
│  [View in Zapier] [Disconnect]                             │
└────────────────────────────────────────────────────────────┘
```

"Connect" → opens OAuth authorization flow. "View in Zapier" → deep link to Zapier app dashboard. "Disconnect" → revokes the OAuth token for Zapier.

---

## API Extensions

```
GET  /api/v1/webhooks                  → list webhooks (existing, in white-label-api spec)
POST /api/v1/webhooks                  → create webhook (used by Zapier REST hook subscribe)
DELETE /api/v1/webhooks/:id            → delete webhook (used by unsubscribe)

GET  /api/v1/customers?email=          → search customer by email (for Zapier/Make search modules)
GET  /api/v1/projects?name=            → search project by name
GET  /api/v1/invoices?number=          → search invoice by number
GET  /api/v1/customers                  → list customers (for dynamic Zapier fields)
GET  /api/v1/projects                   → list projects (for dynamic Zapier fields)
GET  /api/v1/invoices                   → list invoices (for dynamic Zapier fields)
GET  /api/v1/leads                      → list leads (for dynamic Zapier fields)
GET  /api/v1/tasks                      → list tasks (for dynamic Zapier fields)
```

The unfiltered list endpoints use the same tenant isolation, scope checks, and cursor pagination as the existing leads endpoint; they were added to keep dynamic Zapier fields backed by real public-API routes.

These endpoints are part of the public API (spec 39 `tenant-public-api`) with standard rate limiting and scope-based auth.

---

## Architecture Decisions

| Decision | Choice | Reason |
|----------|--------|--------|
| Native app listing | Not just raw API | Native Zapier/Make listings provide structured triggers, action builders, and OAuth flows with better UX than raw webhook setup; reaches non-technical users |
| REST hooks for events | Not polling | Polling introduces latency (Zapier free-tier polls every 15min); REST hooks deliver events instantly for Business+ use cases |
| Same OAuth app for both | Not separate apps | Single OAuth flow (`/api/oauth/authorize`) serves both platforms; code reuse; single token revocation path |
| Scope-limited OAuth tokens | Full write access not granted | Zapier/Make tokens get explicit scopes; not admin/settings access; prevents automation errors from affecting critical settings |
| Business+ tier only | Not Freelancer | Integration platforms target power users and teams; Freelancer use case is served by manual export; tier gate is appropriate |
