External tool adapters

Direct provider adapters let workflows execute external actions using organization-scoped credentials stored as integration connections (never embedded in workflow JSON). For REST details, see integration connections in the API reference.

Runtime path:
integration_id (plus aliases) + optional connection_key → resolve IntegrationConnection for the current organization → adapter executes action with payload.

For AI agents and automation authors

Use this section as a machine-readable contract when generating or editing workflows.

  1. Discover step types: GET /api/v1/workflows/step_types returns integration_action and all other step schemas.
  2. Discover connections: GET /api/v1/integration_connections lists providers and external_key per row (no secret values). Match connection_key in the step to external_key on the connection (omit or use default).
  3. Allowed actions only: The server allowlists (provider, action) pairs. If the pair is not listed below, the API returns unsupported_integration_action.
  4. Templating: String fields inside payload may include {{variable}} placeholders; the runtime merges enrollment or workflow data.
  5. Never put secrets in steps: API keys, OAuth tokens, and webhook URLs belong in Settings → Integrations (or POST/PATCH /api/v1/integration_connections), not in workflow JSON.
  6. Test vs live: POST /api/v1/workflows/:id/test runs in test mode and stubs integration_action (no real provider call). Real calls happen during live enrollment execution or any server path that runs the step with mode: :live.
Limitation of POST /api/v1/integration_connections/:id/test_connection: It validates that credentials decrypt and satisfy required key shapes. It does not prove OAuth scopes, Slack channel membership, or SendGrid sender verification. Those surface as provider HTTP errors during live runs.

REST API (org-scoped)

All endpoints below require an authenticated session: Authorization: Bearer <JWT> (same token you use for other /api/v1 calls after login).

MethodPathPurpose
GET/api/v1/workflows/step_typesJSON schemas for every step type, including integration_action.
GET/api/v1/integration_connectionsList connections (provider, external_key, status; no secrets).
GET/api/v1/integration_connections/:idShow one connection.
POST/api/v1/integration_connectionsCreate (body: provider, external_key optional defaulting to default, credentials object).
PATCH/api/v1/integration_connections/:idUpdate credentials or metadata.
DELETE/api/v1/integration_connections/:idRemove a connection.
POST/api/v1/integration_connections/:id/refresh_tokenOAuth refresh where supported (provider-specific).
POST/api/v1/integration_connections/:id/test_connectionShape / decrypt validation (see warning above).
POST/api/v1/workflows/:id/testRun workflow in test mode; integration_action is stubbed.

Workflow test vs live: output fields

Downstream steps can branch on these keys in enrollment data.

Test run (POST /api/v1/workflows/:id/test, test context):

Live run (enrollment execution, non-test):

Allowlisted (provider, action) pairs

The server rejects any pair not in this table with unsupported_integration_action.

ProviderActions
google_workspacesend_email
microsoft365send_email
slackpost_message
salesforcecrm_apply
hubspotcrm_apply
stripeupsert_customer_metadata
sendgridsend_email
zoomcreate_meeting
zendeskcreate_ticket
gonglist_users, add_call

Workflow step: integration_action

Persisted shape: step metadata at top level; parameters under config (the API may merge legacy top-level fields into config on save).

{
  "type": "integration_action",
  "name": "Notify sales",
  "config": {
    "integration_id": "slack",
    "action": "post_message",
    "connection_key": "default",
    "payload": {
      "text": "Lead {{email}} reached score {{score}}",
      "channel": "C0123456789"
    }
  }
}

Fields: integration_id (required), action (required), payload (object, optional but usually required by the action), connection_key (optional; defaults to default).

Provider alias map (integration_id → internal provider)

The server normalizes aliases before resolving connections:

Acceptable integration_id valuesInternal provider
google_workspace, gmail, google, google-workspacegoogle_workspace
microsoft365, office365, outlookmicrosoft365
sendgrid, sgsendgrid
salesforce, sf, sfdcsalesforce
hubspot, hshubspot
slackslack
stripestripe
zoomzoom
zendeskzendesk
gonggong

Allowed actions and payloads (live execution)

Each row lists the action string and the payload keys the adapter reads (string keys). Optional keys are marked.

google_workspace — send_email

Credentials (stored): access_token (required for validation). For long-lived runs, also store refresh_token, client_id, client_secret, and optionally access_token_expires_at so the server can refresh before Gmail calls. Optional sender_email used as RFC822 From when payload.from is omitted.

"payload": {
  "to": "recipient@example.com",
  "subject": "Subject line",
  "text": "Plain body (required)",
  "from": "optional@verified-domain.com"
}

microsoft365 — send_email

Credentials: access_token (Graph token with mail send). Same optional OAuth refresh fields as Google when configured.

"payload": {
  "to": "recipient@example.com",
  "subject": "Subject",
  "text": "Body (or use key body instead of text)"
}

sendgrid — send_email

Credentials: api_key (required). Store default_from_email for verified sender default. Optional headers object is passed through to SendGrid v3 when present.

Plain body may use text or body; HTML may use html or body_html. At least one text or HTML block is built into the SendGrid request.

"payload": {
  "to": "recipient@example.com",
  "subject": "Subject",
  "text": "Plain text",
  "html": "<p>Optional HTML</p>",
  "from": "optional override; must be verified in SendGrid",
  "headers": { "X-Entity-Ref-ID": "optional-string" }
}

Nurture email: The nurture_email_send step type (not integration_action) can send via the org’s SendGrid connection when one exists; see workflow step schemas from GET /api/v1/workflows/step_types.

slack — post_message

Credentials (either): webhook_url or bot_token. For bot mode, include default_channel (channel ID like C… or a channel name Slack accepts) unless every payload supplies channel.

"payload": {
  "text": "Message text (required)",
  "channel": "optional when using bot_token; overrides default_channel"
}

hubspot — crm_apply

Credentials: access_token (private app token with CRM scopes).

"payload": {
  "crm_action": "create | update | delete",
  "field": "hubspot_property_name (e.g. lifecyclestage)",
  "value": "new value",
  "runtime": {
    "email": "contact@example.com"
  }
}

HubSpot locates the contact by runtime.email (also checks Email, recipient_email).

salesforce — crm_apply

Credentials: instance_url, access_token; optional api_version (default 59.0).

"payload": {
  "crm_action": "create | update | delete",
  "field": "Salesforce field API name",
  "value": "new value",
  "sobject": "Lead",
  "runtime": {
    "email": "lead@example.com"
  }
}

stripe — upsert_customer_metadata

Credentials: secret_key.

"payload": {
  "email": "customer@example.com",
  "metadata": {
    "plan_tier": "pro",
    "lead_score": "88"
  }
}

zoom — create_meeting

Credentials: access_token.

"payload": {
  "topic": "Meeting title (required)",
  "start_time": "optional ISO8601 for scheduled meeting",
  "duration_minutes": 30
}

zendesk — create_ticket

Credentials: subdomain, email, api_token.

"payload": {
  "subject": "required",
  "description": "required (ticket body)",
  "requester_email": "optional",
  "priority": "optional (urgent, high, normal, low)"
}

gong — list_users or add_call

Credentials: access_key, secret_key; optional base_url (defaults to Gong API host).

"action": "list_users",
"payload": { "cursor": "optional pagination cursor" }
"action": "add_call",
"payload": {
  "call": { "...": "Gong v2 calls object; must include clientUniqueId" }
}

Gong payloads follow Gong’s public API for call uploads.

Credential shape checklist (server validation only)

These keys are required at minimum to save a connection; providers may need additional optional fields for successful live calls (see per-provider sections above).

ProviderRequired keys (shape)
slackwebhook_url or bot_token
google_workspace, microsoft365, hubspot, zoomaccess_token
salesforceinstance_url, access_token
sendgridapi_key (add default_from_email for sends)
stripesecret_key
zendesksubdomain, email, api_token
gongaccess_key, secret_key

Common runtime errors (for agents to handle)

Supported providers (summary)

Provider keyPrimary useAdapter actions
google_workspace (alias: gmail)Gmail API sendsend_email
microsoft365 (alias: office365, outlook)Graph mail sendsend_email
slackWebhook or bot chat postpost_message
salesforce (alias: sf, sfdc)CRM RESTcrm_apply
hubspot (alias: hs)CRM contactscrm_apply
stripeCustomer metadataupsert_customer_metadata
sendgrid (alias: sg)Transactional emailsend_email
zoomMeetingscreate_meeting
zendeskTicketscreate_ticket
gongUsers / call uploadlist_users, add_call

Credential and security guardrails

Operational guidance

Related: Manual adapter setup (UI) · Workflow builder · API: integration connections · Production readiness.