Hydra logoHydra · Hydra Support

Automation

Trigger flows from visitor events

Build flows that fire when a visitor calls hydra.track() or hydra.identify() from a tracking-enabled widget — with filters on event name and properties, and template tokens for everything the call carried.

Trigger flows from visitor events

Any widget with Site Activity tracking on can drive Hydra flows. When a visitor on your site calls hydra.track('event_name', {…}) or hydra.identify(user_id, {…}), Hydra emits a flow event you can build automations around — sending an email when someone requests a demo, pinging Slack when an enterprise account asks for a quote, opening a ticket the moment a logged-in customer opens the support widget. The trigger event carries the page URL, the referrer, the anonymous visitor ID, the stitched customer record (if known), and every primitive property the call passed.

Flows are configured at Flows → New Flow. The two new triggers — A site visitor fired a custom event and A site visitor identified themselves — appear in the Designer's trigger list once any widget on the tenant has tracking turned on.

The two triggers

There are two new flow trigger events, both keyed to the widget that emitted them:

  • visitor.custom_event — fires whenever your site code calls hydra.track('event_name', {…properties}) from a page where a tracking-enabled widget is loaded. Filter on event_name in the trigger conditions to scope the flow to one specific custom event (e.g. event_name eq "demo_requested"). Without an event-name filter, the flow fires on every custom event from every tracking-enabled widget.
  • visitor.identified — fires whenever your site code calls hydra.identify(user_id, {…traits}). Useful for "an anonymous visitor just told us who they are" workflows — kick off an onboarding email, open a ticket against the new contact record, or push the identification into a downstream system.

Both events come from the same ingest endpoint as the rest of Site Activity, so they only fire when tracking_enabled is on for the widget that loaded the page. With tracking off, no events flow to either the visitor timeline or the flow bus. See Site Activity privacy controls for the per-widget tracking toggle.

What does NOT trigger a flow

Two other Site Activity event types — pageview and session_start — do not fire flow triggers. They're high-volume, signal-poor events; running a flow on every pageview would burn through your invocation budget for very little gain. Only intent-bearing custom and identify events are emitted to the flow bus.

If you want a flow on something pageview-shaped, instrument a custom event from your site code instead. For example, instead of trying to flow on "visitor hit /pricing three times," add this to your pricing-page JS:

const visits = (parseInt(localStorage.getItem('pricing_visits') || '0') + 1)
localStorage.setItem('pricing_visits', visits)
if (visits >= 3) {
  hydra.track('pricing_engaged', { visit_count: visits })
}

That gives you a flow trigger on event_name eq "pricing_engaged" with the prop_visit_count available in your action templates.

Template tokens

When a visitor.custom_event or visitor.identified flow runs, the action steps have these tokens available for interpolation in email bodies, webhook payloads, ticket descriptions, and any other free-text field:

  • {{event_type}}custom or identify.
  • {{event_name}} — the user-supplied event name. Empty string for visitor.identified.
  • {{url}} — the page URL the event fired from.
  • {{referrer}} — the previous page, when the browser sent one.
  • {{anon_visitor_id}} — the per-tenant anonymous visitor ID.
  • {{customer_id}} — the stitched customer record's ID, or empty if the visitor is unidentified.
  • {{customer_email}} and {{customer_name}} — pulled from the matching customer record when one exists. Empty otherwise.
  • {{occurred_at}} — ISO timestamp of the event.
  • {{prop_<key>}} — one token per property the call passed. Hydra flattens properties: {plan: 'growth', company: 'Acme'} into {{prop_plan}} and {{prop_company}}. Property keys are lowercased and any non-word characters become underscores — so a property named User-Agent becomes {{prop_user_agent}}.

Property values must be primitives — strings, numbers, booleans, or null. Nested objects and arrays in the properties JSON are dropped from the flow event. (They're still stored on the underlying visitor_events row for the inbox timeline and the export endpoint — they just aren't surfaced as tokens because the flow templater can't traverse nested shapes.)

Available actions

Visitor triggers can drive these action types:

  • Send email — to a fixed address, the matched customer's email, or any address composed from tokens.
  • Webhook — POST a JSON payload to any URL. Useful for Slack incoming webhooks, downstream CRM updates, or your own internal endpoints.
  • Create ticket — open a new support ticket. If {{customer_id}} is populated (the visitor is identified), use it as the requester so the ticket lands on the right contact's timeline.
  • Assign conversation — when a visitor is also mid-conversation, route their conversation to a teammate or channel.
  • Create Jira issue — when the visitor event represents work for engineering or product (a bug-report event, a feature-request event).
  • Create ClickUp task — same idea, for ClickUp-shaped workflows.

These action types are not available on visitor triggers, because visitors aren't first-class records in the same way leads, contacts, accounts, and tickets are:

  • Add lifecycle event — visitors don't have lifecycle stages; lifecycle events live on customer / account records. If the visitor is identified, run a follow-up flow on the matching customer.updated or account.updated event instead.
  • Add internal note — there's no ticket to attach a note to. Pair with Create ticket if you want the note to land somewhere.
  • Update field — there's no record on the visitor event itself to update. If the visitor is identified, the customer record is reachable but isn't the trigger entity, so a downstream flow on customer.updated is the right surface for field changes.

Three recipes

1. Email me when a visitor requests a demo

Site code (on your demo-request form's submit handler):

hydra.track('demo_requested', {
  name: form.name.value,
  email: form.email.value,
  company: form.company.value,
  message: form.message.value,
})

Flow:

  • Trigger — A site visitor fired a custom event (visitor.custom_event)
  • Conditionsevent_name eq "demo_requested"
  • Action — Send email
    • To — your sales address (e.g. sales@example.com)

    • SubjectDemo request from {{prop_company}}

    • Body

      {{prop_name}} from {{prop_company}} just requested a demo.
      
      Their note:
      {{prop_message}}
      
      Reply to: {{prop_email}}
      Came from: {{url}}
      

The flow fires the moment the form submission completes — no polling, no waiting for a CRM sync.

2. Slack-ping the sales channel when an enterprise account asks for a quote

Site code (on your "request a quote" CTA, gated to logged-in customers):

hydra.track('requested_quote', {
  plan: currentUser.plan,        // 'starter' | 'growth' | 'enterprise'
  mrr: currentUser.account.mrr,
  account_name: currentUser.account.name,
})

Flow:

  • Trigger — A site visitor fired a custom event (visitor.custom_event)
  • Conditionsevent_name eq "requested_quote" AND prop_plan eq "enterprise"
  • Action — Webhook
    • URL — your Slack incoming webhook (e.g. https://hooks.slack.com/services/T0…/B0…/…)

    • MethodPOST

    • Body

      {
        "text": "Enterprise quote request from {{prop_account_name}} (MRR ${{prop_mrr}}). Visitor: {{customer_email}}. Page: {{url}}"
      }
      

The prop_plan eq "enterprise" condition means starter and growth quote requests don't ping the channel — only the deals worth a sales rep's attention.

3. Create a ticket when a logged-in customer opens the support widget

Site code (in your authenticated app shell, after login):

hydra.identify(currentUser.id, {
  email: currentUser.email,
  name: currentUser.name,
})

// ...later, when the user clicks the support button:
hydra.track('support_widget_opened', {
  current_url: window.location.href,
  app_section: detectAppSection(),
})

Flow:

  • Trigger — A site visitor fired a custom event (visitor.custom_event)
  • Conditionsevent_name eq "support_widget_opened"
  • Action — Create ticket
    • TitleSupport widget opened by {{customer_name}}

    • Description

      {{customer_name}} ({{customer_email}}) opened the support widget.
      
      Page: {{prop_current_url}}
      App section: {{prop_app_section}}
      
    • Requester{{customer_email}}

Because the visitor identified earlier in the session, {{customer_email}} and {{customer_name}} are populated by the time the support_widget_opened event fires. The ticket lands on the right contact's timeline automatically.

How identification stitches into the flow event

When a visitor.custom_event or visitor.identified event fires, Hydra runs one lookup against the customers table by anon_visitor_id:

  • If a customer row exists for that anon ID, {{customer_id}}, {{customer_email}}, and {{customer_name}} are populated from that row.
  • If no customer row exists yet (the visitor is fully anonymous), all three tokens render as empty strings.

The lookup runs once per ingest request, not once per event in the batch — a burst of hydra.track() calls from a single visitor incurs one stitch query, not N. If the lookup fails for any reason, the flow event still fires; the customer tokens just render empty.

Frequently asked questions

Why doesn't my visitor flow fire even though hydra.track() is in the page source? The most common cause is that tracking is off on the widget that's loaded on that page. Site Activity is per-widget and off by default for every newly created widget. Open Widgets → [widget] → Tracking and confirm the toggle is on. If it's off, the widget script's track and identify calls return early in the browser — no ingest request, no flow event.

Can I trigger a flow on a pageview? No. Pageview and session-start events are intentionally not emitted to the flow bus. If you need a pageview-shaped flow, instrument a custom event from your site code (see the example near the top of this article). Custom events are the only pageview-adjacent signal flows can react to.

Do nested objects in my properties payload work as tokens? No. Hydra only flattens primitive property values (strings, numbers, booleans, null) into prop_<key> tokens. A property like properties: {address: {city: 'Arrakeen'}} doesn't expose {{prop_address_city}} — the nested address object is dropped at flow time. Either flatten the shape on your end before calling hydra.track() ({address_city: 'Arrakeen'}) or fetch the full record downstream from the webhook receiver.

Does the bot's first reply count as a visitor event? No. Bot chat messages flow through /api/widget/init and /api/chat, not the Site Activity ingest. Only hydra.track() and hydra.identify() calls (and the auto-emitted pageview / session-start events, which don't reach flows anyway) produce visitor events.

Can I filter on a property value, not just event_name? Yes. Trigger conditions support prop_<key> references the same way they support event_name. The Slack-ping recipe above uses prop_plan eq "enterprise" to scope the flow to enterprise-tier quote requests. Combine multiple condition rows with AND logic in the Designer.

What happens to a flow if I delete the widget that emitted the event? The flow itself is unaffected — flows aren't bound to a specific widget. Future events from other tracking-enabled widgets continue to fire the trigger. Past visitor_events rows for the deleted widget are removed (the FK from visitor_events to widget_configs is ON DELETE CASCADE), but past flow runs in Execution History are preserved as a historical record of what already happened.

Is there a webhook subscription for visitor.custom_event and visitor.identified outside of flows? Yes — both events are listed in the webhook subscription picker at Settings → Webhooks. The webhook delivery is independent of any flow you build; you can subscribe a downstream system to every visitor event without going through the flow Designer at all.