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.
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.
- Discover step types:
GET /api/v1/workflows/step_typesreturnsintegration_actionand all other step schemas. - Discover connections:
GET /api/v1/integration_connectionslists providers andexternal_keyper row (no secret values). Matchconnection_keyin the step toexternal_keyon the connection (omit or usedefault). - Allowed actions only: The server allowlists
(provider, action)pairs. If the pair is not listed below, the API returnsunsupported_integration_action. - Templating: String fields inside
payloadmay include{{variable}}placeholders; the runtime merges enrollment or workflow data. - 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. - Test vs live:
POST /api/v1/workflows/:id/testruns in test mode and stubsintegration_action(no real provider call). Real calls happen during live enrollment execution or any server path that runs the step withmode: :live.
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).
| Method | Path | Purpose |
|---|---|---|
GET | /api/v1/workflows/step_types | JSON schemas for every step type, including integration_action. |
GET | /api/v1/integration_connections | List connections (provider, external_key, status; no secrets). |
GET | /api/v1/integration_connections/:id | Show one connection. |
POST | /api/v1/integration_connections | Create (body: provider, external_key optional defaulting to default, credentials object). |
PATCH | /api/v1/integration_connections/:id | Update credentials or metadata. |
DELETE | /api/v1/integration_connections/:id | Remove a connection. |
POST | /api/v1/integration_connections/:id/refresh_token | OAuth refresh where supported (provider-specific). |
POST | /api/v1/integration_connections/:id/test_connection | Shape / decrypt validation (see warning above). |
POST | /api/v1/workflows/:id/test | Run 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):
integration_action_stub→trueintegration_id,integration_action→ echo from configintegration_payload→ payload after{{variable}}interpolation
Live run (enrollment execution, non-test):
- On success:
integration_action_ok,integration_provider,integration_action,integration_result(adapter return value). - On failure: step
ok: falsewitherrorset to the dispatcher or connector message (e.g.integration_not_configured).
Allowlisted (provider, action) pairs
The server rejects any pair not in this table with unsupported_integration_action.
| Provider | Actions |
|---|---|
google_workspace | send_email |
microsoft365 | send_email |
slack | post_message |
salesforce | crm_apply |
hubspot | crm_apply |
stripe | upsert_customer_metadata |
sendgrid | send_email |
zoom | create_meeting |
zendesk | create_ticket |
gong | list_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 values | Internal provider |
|---|---|
google_workspace, gmail, google, google-workspace | google_workspace |
microsoft365, office365, outlook | microsoft365 |
sendgrid, sg | sendgrid |
salesforce, sf, sfdc | salesforce |
hubspot, hs | hubspot |
slack | slack |
stripe | stripe |
zoom | zoom |
zendesk | zendesk |
gong | gong |
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).
| Provider | Required keys (shape) |
|---|---|
slack | webhook_url or bot_token |
google_workspace, microsoft365, hubspot, zoom | access_token |
salesforce | instance_url, access_token |
sendgrid | api_key (add default_from_email for sends) |
stripe | secret_key |
zendesk | subdomain, email, api_token |
gong | access_key, secret_key |
Common runtime errors (for agents to handle)
integration_not_configured— no active connection for provider +connection_key.unknown_integration_provider—integration_idnot recognized.unsupported_integration_action— action not on the allowlist for that provider.ConnectorError: …— upstream HTTP or validation message from the adapter (scopes, sender identity, channel not found, etc.).
Supported providers (summary)
| Provider key | Primary use | Adapter actions |
|---|---|---|
google_workspace (alias: gmail) | Gmail API send | send_email |
microsoft365 (alias: office365, outlook) | Graph mail send | send_email |
slack | Webhook or bot chat post | post_message |
salesforce (alias: sf, sfdc) | CRM REST | crm_apply |
hubspot (alias: hs) | CRM contacts | crm_apply |
stripe | Customer metadata | upsert_customer_metadata |
sendgrid (alias: sg) | Transactional email | send_email |
zoom | Meetings | create_meeting |
zendesk | Tickets | create_ticket |
gong | Users / call upload | list_users, add_call |
Credential and security guardrails
- Credentials are stored per organization in integration connections and encrypted at rest.
- Workflow steps must reference provider + connection_key only — never raw tokens.
- Runtime resolves
(organization, provider, connection_key); default key isdefault. - List APIs return connection metadata, not decrypted secrets.
- HTTP calls use connector-level timeouts; transient errors may be retried at the HTTP layer where implemented.
Operational guidance
- Prefer one active connection per (provider, external_key) per org to keep routing predictable.
- Before turning on high-volume live workflows, run a single live enrollment or manual integration test in a sandbox tenant.
- When rotating credentials, create a new
external_key, repoint workflows, then disable the old connection.
Related: Manual adapter setup (UI) · Workflow builder · API: integration connections · Production readiness.