Skip to main content
Automations turn hub events into actions. Instead of a single linear drip, you build a flowchart: a trigger starts a run, and each contact moves through nodes — send an email, wait, branch on a condition, add or remove a tag, hand off to a drip sequence, call a webhook, or exit. A drip sequence becomes one node inside an automation. A one-step automation (one trigger, one action) is a “rule”. Same engine for both.
WHEN a member joins
  → send "Welcome" email
  → wait 2 days
  → has the "engaged" tag?
      yes → add "vip" tag
      no  → send "Need help?" email
  → end

How it works

  • Triggers subscribe an automation to an event type (e.g. a member joining, a purchase, a tag being applied, or a custom event you fire yourself).
  • Each matching contact gets an enrollment — an independent run through the published graph. Enrollments are idempotent (one event enrolls a contact at most once) and honor a per-automation re-entry policy (never, after_exit, interval).
  • A published version is an immutable snapshot. Editing a draft and re-publishing creates a new version; in-flight enrollments keep running on the version they started on.

Node types

NodeWhat it does
send_emailSend a transactional email (template + merge context).
waitPause the run for a duration (e.g. "2 days").
branchEvaluate a segment condition tree → yes / no edge.
add_tag / remove_tagTag or untag the contact (idempotent).
enroll_in_sequenceHand the contact off to a drip campaign (terminal).
webhookPOST a signed payload to a configured endpoint (SSRF-guarded, async with retries).
exitEnd the run.

Authoring lifecycle

Automations are hub-scoped and require a team owner. The lifecycle is draft → publish (snapshot + validate) → activate.
POST   /api/v1/teams/{team_id}/hubs/{hub_id}/automations              create draft
GET    /api/v1/teams/{team_id}/hubs/{hub_id}/automations              list
GET    /api/v1/teams/{team_id}/hubs/{hub_id}/automations/{id}         retrieve
PATCH  /api/v1/teams/{team_id}/hubs/{hub_id}/automations/{id}         update draft
POST   /api/v1/teams/{team_id}/hubs/{hub_id}/automations/{id}/publish snapshot + validate graph
POST   /api/v1/teams/{team_id}/hubs/{hub_id}/automations/{id}/activate   draft/paused → active
POST   /api/v1/teams/{team_id}/hubs/{hub_id}/automations/{id}/deactivate active → paused
GET    /api/v1/teams/{team_id}/hubs/{hub_id}/automations/{id}/versions   list versions
A create body carries the graph in attributes.definition:
{
  "data": {
    "type": "automations",
    "attributes": {
      "name": "Welcome flow",
      "re_entry_mode": "never",
      "definition": {
        "triggers": [{ "event_type": "custom.member_joined" }],
        "nodes": [
          { "id": "welcome",  "type": "send_email", "config": { "template_ref": "welcome-email" } },
          { "id": "wait2d",   "type": "wait",       "config": { "duration": "2 days" } },
          { "id": "checktag", "type": "branch",
            "config": { "condition_tree": { "groups": [ { "conditions": [
              { "type": "has_tag", "operator": "has", "value": { "tag_slug": "engaged" } }
            ] } ] } } },
          { "id": "tagvip",   "type": "add_tag",    "config": { "tag_slug": "vip" } },
          { "id": "nudge",    "type": "send_email", "config": { "template_ref": "nudge-email" } },
          { "id": "done",     "type": "exit",       "config": {} }
        ],
        "edges": [
          { "from": "welcome",  "to": "wait2d" },
          { "from": "wait2d",   "to": "checktag" },
          { "from": "checktag", "to": "tagvip", "branch_label": "yes" },
          { "from": "checktag", "to": "nudge",  "branch_label": "no" },
          { "from": "tagvip",   "to": "done" },
          { "from": "nudge",    "to": "done" }
        ]
      }
    }
  }
}
publish validates the graph (reachability, branch edges must be labeled, condition trees compile against your tags/segments) and returns a versioned snapshot. Branch tag_slug values are resolved to IDs at publish time, so the referenced tags must already exist.

Enrolling contacts

Most enrollments happen automatically when a trigger event fires. You can also enroll manually, or fire your own custom event from an integration.
POST /api/v1/teams/{team_id}/hubs/{hub_id}/automations/{id}/enrollments   manual enroll a contact
GET  /api/v1/teams/{team_id}/hubs/{hub_id}/automations/{id}/enrollments   observe runs (?filter[status]=stuck)
POST /api/v1/teams/{team_id}/hubs/{hub_id}/automations/events             fire a custom event
POST /api/v1/teams/{team_id}/hubs/{hub_id}/automations/{id}/test          dry-run a contact (no side effects)
Custom events require an idempotency_key (deduplicated per team) and identify the contact:
{
  "event_type": "custom.member_joined",
  "team_contact_id": "…",
  "idempotency_key": "join-2026-06-09-abc",
  "payload": {}
}
Returns 202 on a new event, 200 on a duplicate. Use /test to dry-run a contact through the published graph: it runs the real branch logic and reports the step trace without sending email, applying tags, or firing webhooks.
/test is the safe way to confirm routing before you turn an automation on — branches evaluate for real, but every side effect is stubbed.

Webhook endpoints

The webhook node POSTs a signed JSON payload to an endpoint you register. Targets are validated against an SSRF guard (private, loopback, link-local, and cloud-metadata addresses are rejected), the signing secret is encrypted at rest and never returned, and delivery is asynchronous with exponential-backoff retries.
POST   /api/v1/teams/{team_id}/hubs/{hub_id}/webhook-endpoints       create (returns no secret)
GET    /api/v1/teams/{team_id}/hubs/{hub_id}/webhook-endpoints       list
DELETE /api/v1/teams/{team_id}/hubs/{hub_id}/webhook-endpoints/{id}  delete

Reliability

  • Triggers are delivered from a durable event outbox, so a server hiccup never silently drops an enrollment.
  • Each node executes at most once per enrollment (idempotency keys); failed steps retry, then move to a dead-letter queue rather than looping.
  • wait is real scheduling — a run resumes when its timer is due, even days later.