Triggers
A trigger is the event that causes an automation rule to fire. Each trigger is scoped to a specific entity
via its trigger_id — for example, a particular form, a particular tag, or a particular pipeline stage.
Gordon CRM supports eight trigger types.
Entity-Based Triggers
These triggers fire when a contact interacts with a specific entity in the CRM.
"Any" option: Each entity-based trigger supports an "Any" option (e.g. "Any Form", "Any Event", "Any Tag", "Any Product") that fires the rule regardless of which specific entity was involved. This is useful for broad rules like "When any form is submitted → send notification."
Form Submitted
| trigger_type | form_submission |
| trigger_id | The UUID of the specific form |
| Fires when | A contact submits a headless form (see Context-Aware Double Opt-In below) |
| Called from | /api/forms/[id] (new contacts) or /api/forms/verify (existing contacts) |
Context-Aware Double Opt-In
Gordon CRM uses a split processing model based on whether the submitter already exists in the workspace:
-
New contact — The contact is created immediately, the submission is logged, and the automation fires right away in the form submission endpoint (
/api/forms/[id]). No email verification is required because a brand-new email address has no prior data to protect. -
Existing contact — The submission is staged in a
pending_form_submissionstable with a secure token, and a verification email is sent. The automation only fires after the contact clicks the verification link (/api/forms/verify), which prevents impersonation attacks that could overwrite existing contact data.
Example use case: When someone submits the "Request a Demo" form → add the "Demo Request" tag.
Event Registration
| trigger_type | event_registration |
| trigger_id | The UUID of the CRM event |
| Fires when | A contact registers for an event (manual RSVP or Eventbrite ticket purchase) |
| Called from | /api/rsvp/[id] (manual events) and /api/webhooks/eventbrite (Eventbrite events) |
This trigger fires from both event sources:
-
Manual events — When a contact submits the public RSVP form, the automation fires after the registration record is created or re-activated.
-
Eventbrite events — When the Eventbrite webhook handler (
order.placedorattendee.updated) processes a registration, it also callsprocessAutomationTriggerwith the CRM event ID. The handler deduplicates per-contact — if a single order contains multiple tickets for the same contact, the automation fires only once.
Note: For Eventbrite events, the
trigger_idis the CRM event UUID (not the Eventbrite event ID). This means the same automation rule works regardless of whether the registration came through the native RSVP form or an Eventbrite webhook.
Example use case: When someone registers for "Spring Gala 2026" → enroll in the "Event Reminder" campaign.
See also: Events → Registration & Public Pages for the full registration flow, and Events → Eventbrite Integration for webhook processing details.
Product Purchase
| trigger_type | product_purchase |
| trigger_id | The UUID of the purchased product |
| Fires when | A Stripe checkout.session.completed webhook is received for a linked product |
| Called from | /api/webhooks/stripe — after the payment is verified and the contact is matched |
Example use case: When someone purchases "Annual Membership" → add the "Member" tag.
Tag-Based Triggers
These triggers fire when a tag is added to or removed from a contact. They can be caused by manual user action or by a preceding automation action.
Tag Applied
| trigger_type | tag_applied |
| trigger_id | The UUID of the tag that was applied |
| Fires when | The specified tag is added to a contact |
| Called from | The processAutomationTrigger engine itself (cascading), or manual tag operations |
Example use case: When "VIP" tag is applied → enroll in the "VIP Welcome Sequence" campaign.
Tag Removed
| trigger_type | tag_removed |
| trigger_id | The UUID of the tag that was removed |
| Fires when | The specified tag is removed from a contact |
| Called from | The processAutomationTrigger engine itself (cascading), or manual tag operations |
Example use case: When "Active Member" tag is removed → remove from the "Members-Only Newsletter" campaign.
Deal Triggers
These triggers fire when a deal progresses through a sales pipeline. Both use a cascading pipeline → stage/status picker in the UI.
Deal Stage Changed
| trigger_type | deal_stage_changed |
| trigger_id | See patterns below |
| Fires when | A deal is moved to a matching stage (via drag-and-drop on the Kanban board or manual edit) |
| Called from | Deal update server actions |
The UI uses a two-tier picker: first select a pipeline (or "Any Pipeline"), then select a stage within that pipeline (or "Any Stage").
| Pipeline | Stage | trigger_id Pattern |
|---|---|---|
| Any Pipeline | Any Stage | __any__ |
| Specific Pipeline | Any Stage | {pipelineId}__any_stage__ |
| Specific Pipeline | Specific Stage | {stageId} |
Example use case: When a deal moves to the "Proposal Sent" stage → add the "Proposal Sent" tag.
Deal Status Changed
| trigger_type | deal_status_changed |
| trigger_id | See patterns below |
| Fires when | A deal's terminal status is changed to Won or Lost |
| Called from | Deal update server actions |
The UI uses a two-tier picker: first select a pipeline (or "Any Pipeline"), then select a status (Won, Lost, or "Any Status").
| Pipeline | Status | trigger_id Pattern |
|---|---|---|
| Any Pipeline | Any Status | __any__ |
| Any Pipeline | Won or Lost | __any___won or __any___lost |
| Specific Pipeline | Any Status | {pipelineId}__any_status__ |
| Specific Pipeline | Won or Lost | {pipelineId}_won or {pipelineId}_lost |
Example use case: When any deal is marked as "Won" → send notification to the sales manager.
Lifecycle Triggers
Contact Birthday
| trigger_type | contact_birthday |
| trigger_id | __birthday__ (static sentinel value) |
| Fires when | The daily birthday cron sweeper finds contacts whose birthday matches today |
| Called from | /api/cron/birthday-sweeper |
Unlike other triggers, this one does not require a specific entity to be selected in the UI — it fires
globally for all contacts in the workspace whose birth_month and birth_day fields match the current date.
Example use case: On a contact's birthday → enroll in the "Birthday Greeting" campaign.
Trigger Resolution
When a trigger fires, the engine resolves human-readable names for display purposes:
| Trigger Type | Resolution Strategy |
|---|---|
form_submission | Form name from forms table (or "Any Form") |
event_registration | Event name from events table (or "Any Event") |
product_purchase | Product name from products table (or "Any Product") |
tag_applied / tag_removed | Tag name from tags table (or "Any Tag") |
deal_stage_changed | "Pipeline Name: Stage Name", "Pipeline Name: Any Stage", or "Any Pipeline: Any Stage" |
deal_status_changed | "Pipeline Name: Won/Lost", "Pipeline Name: Any Status", "Any Pipeline: Won/Lost", or "Any Pipeline: Any Status" |
contact_birthday | Static label: "Contact's Birthday" |