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.
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.
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 | The UUID of the pipeline stage |
| Fires when | A deal is moved to the specified stage (via drag-and-drop on the Kanban board or manual edit) |
| Called from | Deal update server actions |
The UI uses a cascading picker: first select a pipeline, then select a stage within that pipeline.
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 | Composite: {pipeline_id}_{status} (e.g. abc123_won) |
| Fires when | A deal's terminal status is changed to Won or Lost |
| Called from | Deal update server actions |
The trigger_id for this trigger type is a composite key that combines the pipeline UUID and the status
(won or lost) with an underscore separator. This allows different automations for different pipelines.
Example use case: When a deal is marked as "Won" in the "Enterprise Sales" pipeline → add the "Customer" tag.
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 |
event_registration | Event name from events table |
product_purchase | Product name from products table |
tag_applied / tag_removed | Tag name from tags table |
deal_stage_changed | "Pipeline Name: Stage Name" from joined pipeline_stages + pipelines |
deal_status_changed | "Pipeline Name: Won/Lost" by parsing the composite trigger_id |
contact_birthday | Static label: "Contact's Birthday" |