# How Does Twenty CRM Integrate with Zapier: A Complete Technical Guide

> Learn how Twenty CRM integrates with Zapier using its Node.js package for webhook triggers and GraphQL actions. Automate workflows with over 3,000 apps today.

- Repository: [Twenty/twenty](https://github.com/twentyhq/twenty)
- Tags: how-to-guide
- Published: 2026-03-27

---

**Twenty CRM integrates with Zapier through a dedicated Node.js package (`packages/twenty-zapier`) that exposes webhook-based triggers for record changes and GraphQL-powered CRUD actions, authenticating via API keys to enable automation with 3,000+ apps.**

The `twentyhq/twenty` repository ships a standalone Zapier integration that connects the open-source CRM to Zapier's ecosystem without modifying the core application. This integration uses the **Zapier Platform Core** SDK to translate Twenty's GraphQL API into triggers and actions that non-technical users can configure in Zaps, while developers maintain full control via standard CLI commands.

## Core Architecture and Package Structure

The integration lives in `packages/twenty-zapier` and operates independently from Twenty's frontend and backend services. After building the package with `yarn build`, developers deploy it using standard Zapier CLI commands (`zapier login`, `zapier push`).

**Key architectural components include:**

- **[`src/index.ts`](https://github.com/twentyhq/twenty/blob/main/src/index.ts)** — The package entry point that exports the version, authentication module, triggers, and creates to the Zapier CLI
- **[`src/authentication.ts`](https://github.com/twentyhq/twenty/blob/main/src/authentication.ts)** — Implements custom API-key authentication with optional self-hosted URL support
- **[`src/triggers/trigger_record.ts`](https://github.com/twentyhq/twenty/blob/main/src/triggers/trigger_record.ts)** — Defines webhook-based triggers for record lifecycle events
- **[`src/creates/crud_record.ts`](https://github.com/twentyhq/twenty/blob/main/src/creates/crud_record.ts)** — Provides Create, Update, and Delete actions with dynamic field mapping
- **[`src/utils/requestDb.ts`](https://github.com/twentyhq/twenty/blob/main/src/utils/requestDb.ts)** — A unified request wrapper that handles GraphQL queries, metadata fetching, and REST calls to Twenty's API

## Authentication and Connection Setup

Twenty implements **custom authentication** in [`src/authentication.ts`](https://github.com/twentyhq/twenty/blob/main/src/authentication.ts) that accepts an API key and optional self-hosted instance URL. When a user connects their Twenty account in Zapier, the integration verifies the credentials by executing a lightweight GraphQL query against the `currentWorkspace` endpoint.

The authentication configuration stores the API key as a Bearer token and constructs the base URL from either the user-provided `apiUrl` or the default `SERVER_BASE_URL` environment variable:

```typescript
// src/authentication.ts
export default {
  type: 'custom',
  test: async (z, bundle) => {
    // Verify the API key by querying the current workspace
    return await requestDb({
      z,
      bundle,
      query: 'query currentWorkspace {currentWorkspace {id displayName}}',
      endpoint: 'metadata',
    });
  },
  fields: [
    {
      key: 'apiKey',
      required: true,
      label: 'Api Key',
      type: 'string',
      helpText:
        'Create an API key in your Twenty workspace (Settings → APIs).',
    },
    {
      key: 'apiUrl',
      required: false,
      label: 'Self‑hosted instance URL',
      type: 'string',
      placeholder: 'https://crm.custom-url.com',
      helpText: 'Set this only if you self‑host Twenty.',
    },
  ],
  connectionLabel: '{{data.currentWorkspace.displayName}}',
};

```

The `connectionLabel` dynamically displays the workspace name in Zapier's UI, ensuring users can distinguish between multiple Twenty connections.

## Real-Time Triggers via Webhooks

Unlike polling-based integrations, Twenty uses **REST Hooks** (webhooks) for real-time event delivery. When a user enables a trigger, Zapier registers a unique webhook URL with Twenty's backend via the [`call-webhook-jobs.job.ts`](https://github.com/twentyhq/twenty/blob/main/call-webhook-jobs.job.ts) service. Twenty then pushes record change events directly to Zapier as they occur.

The trigger implementation in [`src/triggers/trigger_record.ts`](https://github.com/twentyhq/twenty/blob/main/src/triggers/trigger_record.ts) supports four operation types: **CREATED**, **UPDATED**, **DELETED**, and **DESTROYED**. The `operation` field allows users to filter which events should start their Zap:

```typescript
// src/triggers/trigger_record.ts
export default {
  key: 'trigger_record',
  noun: 'Record',
  display: {
    label: 'Record Trigger',
    description: 'Triggers when a Record is created, updated, deleted or destroyed.',
  },
  operation: {
    inputFields: [
      {
        key: 'nameSingular',
        required: true,
        label: 'Record Name',
        dynamic: `${findObjectNamesSingularKey}.nameSingular.labelSingular`,
        altersDynamicFields: true,
      },
      {
        key: 'operation',
        required: true,
        label: 'Operation',
        choices: { CREATED: 'CREATED', UPDATED: 'UPDATED', DELETED: 'DELETED', DESTROYED: 'DESTROYED' },
        altersDynamicFields: true,
      },
    ],
    type: 'hook',
    performSubscribe,
    performUnsubscribe,
    perform,
    performList,
    sample: { id: 'f75f6b2e-9442-4c72-aa95-47d8e5ec8cb3', createdAt: '2023-10-19T07:37:25.306Z' },
    outputFields: [{ key: 'id', label: 'ID' }, { key: 'createdAt', label: 'Created At' }],
  },
};

```

The `dynamic` property on the `nameSingular` field fetches available object types from Twenty's metadata API at runtime, ensuring the dropdown always reflects the current workspace schema.

## CRUD Actions and Dynamic Schema

Twenty exposes Create, Update, and Delete operations through a single configurable action defined in [`src/creates/crud_record.ts`](https://github.com/twentyhq/twenty/blob/main/src/creates/crud_record.ts). Rather than hardcoding fields for specific objects, the integration uses **dynamic field generation** to present form inputs based on the selected record type and operation.

The `computeFields` function (referenced in the operation configuration) introspects the workspace schema via `requestSchema` to build GraphQL mutations with the correct input variables:

```typescript
// src/creates/crud_record.ts
export default {
  key: 'crud_record',
  noun: 'Record',
  display: {
    label: 'Create, Update or Delete Record',
    description: 'Create, Update or Delete a Record in Twenty.',
  },
  operation: {
    inputFields: [
      {
        key: 'nameSingular',
        required: true,
        label: 'Record Name',
        dynamic: `${findObjectNamesSingularKey}.nameSingular.labelSingular`,
        altersDynamicFields: true,
      },
      {
        key: 'crudZapierOperation',
        required: true,
        label: 'Operation',
        choices: {
          CREATED: 'CREATED',
          UPDATED: 'UPDATED',
          DELETED: 'DELETED',
        },
        altersDynamicFields: true,
      },
      // Dynamic fields based on selected operation are computed at runtime
      computeFields,
    ],
    perform,
    sample: { id: '179ed459-79cf-41d9-ab85-96397fa8e936' },
  },
};

```

When a user selects "CREATED" or "UPDATED", the integration renders input fields matching the target object's schema. For "DELETED" operations, it typically requires only the record ID.

## The Request Layer: Connecting to Twenty's API

All network communication flows through [`src/utils/requestDb.ts`](https://github.com/twentyhq/twenty/blob/main/src/utils/requestDb.ts), a centralized request wrapper that standardizes headers, error handling, and endpoint selection. This utility supports three endpoints: `/graphql`, `/metadata`, and `/rest`, defaulting to GraphQL for most operations.

The function automatically injects the Bearer token from `bundle.authData.apiKey` and parses GraphQL error responses into Zapier-friendly exceptions:

```typescript
// src/utils/requestDb.ts
export const requestDb = async ({ z, bundle, query, endpoint = 'graphql' }) => {
  const options = {
    url: `${bundle.authData.apiUrl || process.env.SERVER_BASE_URL}/${endpoint}`,
    method: 'POST',
    headers: {
      'Content-Type': 'application/json',
      Authorization: `Bearer ${bundle.authData.apiKey}`,
    },
    body: { query },
  };

  return z.request(options).then(resp => {
    const result = resp.json;
    if (result.errors) {
      throw new z.errors.Error(`GraphQL error: ${JSON.stringify(result.errors)}`);
    }
    resp.throwForStatus();
    return result;
  });
};

```

This layer ensures consistent error handling across triggers and actions, converting HTTP status codes and GraphQL validation errors into actionable messages within the Zapier UI.

## Data Flow and Webhook Lifecycle

The complete integration flow follows this sequence when a user activates a Twenty trigger in a Zap:

1. **Authentication Verification** — Zapier calls `authentication.test`, which executes `requestDb` with a `currentWorkspace` query to validate the API key before enabling the Zap.
2. **Webhook Registration** — When the Zap is turned on, Zapier invokes `performSubscribe` in [`trigger_record.ts`](https://github.com/twentyhq/twenty/blob/main/trigger_record.ts), which registers a webhook URL with Twenty's backend job [`call-webhook-jobs.job.ts`](https://github.com/twentyhq/twenty/blob/main/call-webhook-jobs.job.ts).
3. **Event Delivery** — When a record changes in Twenty, the backend posts the event payload to the registered Zapier webhook URL.
4. **Data Processing** — Zapier receives the payload and executes the trigger's `perform` or `performList` method, returning normalized data to subsequent Zap steps.
5. **CRUD Execution** — For action steps, Zapier builds a GraphQL mutation via `requestDb`, sending a POST request to `/graphql` with the user-supplied field values.

## Summary

- **Twenty CRM integrates with Zapier** via the standalone `packages/twenty-zapier` package using the Zapier Platform Core SDK.
- **Authentication uses API keys** passed as Bearer tokens, with support for self-hosted Twenty instances via configurable base URLs.
- **Triggers are webhook-based** (not polling), registering with Twenty's [`call-webhook-jobs.job.ts`](https://github.com/twentyhq/twenty/blob/main/call-webhook-jobs.job.ts) backend service to receive real-time record change events for create, update, delete, and destroy operations.
- **Actions support full CRUD** operations on any Twenty object, using dynamic field generation to stay synchronized with workspace schemas.
- **All API calls** route through [`src/utils/requestDb.ts`](https://github.com/twentyhq/twenty/blob/main/src/utils/requestDb.ts), which wraps `z.request` to handle GraphQL queries, metadata fetching, and error normalization.

## Frequently Asked Questions

### Does Twenty CRM require a paid Zapier plan to integrate?

No, the Twenty CRM integration works with Zapier's free plan, though webhook-based triggers (used by Twenty) require Zapier's "Starter" tier or higher to run in live Zaps. You can build and test Zaps with Twenty on any plan, but automated execution of webhook triggers follows Zapier's standard pricing tiers for multi-step Zaps.

### Can I use the Twenty Zapier integration with a self-hosted instance?

Yes, the integration explicitly supports self-hosted Twenty instances. During authentication, users can provide a custom `apiUrl` pointing to their self-hosted domain (e.g., `https://crm.company.com`). The `requestDb` utility then routes all GraphQL and metadata requests to this custom endpoint instead of Twenty's cloud service.

### What triggers are available in the Twenty CRM Zapier app?

The integration provides a single **Record Trigger** that covers four operation types: **CREATED**, **UPDATED**, **DELETED**, and **DESTROYED**. Users configure the specific record type (e.g., Company, Person, Custom Object) and operation within the Zap setup. The trigger uses dynamic dropdowns populated from the workspace's metadata API to ensure only valid object types appear.

### How does the integration handle API authentication securely?

Twenty's Zapier app uses **API key authentication** stored as a Bearer token in the `Authorization` header. The keys are encrypted at rest by Zapier's infrastructure and only transmitted over HTTPS to the configured Twenty instance. The integration never logs or exposes the API key in output data, and the `authentication.test` function validates the key's permissions without storing additional session data.