A workflow is a small rule: when this happens, do these things. A deal moves to Closed Won, so create a follow-up task. A contact gets the tag hot, so text them. A form on your website posts a lead, so add them and open a deal. Each workflow reacts to one trigger and runs its steps once, top to bottom, for the record the trigger was about.
Find them in the sidebar under Automations → Workflows.
The three parts of a workflow
Every workflow has the same shape. The editor shows them as three blocks.
- WHEN — the trigger. What has to happen for the workflow to start.
- ONLY IF — an optional condition. If it is false for this run, the workflow is skipped and nothing happens.
- THEN — the steps, in order. Each step either does something (send a text, apply a tag, create a task) or controls the flow (wait, branch).
Save switches the workflow on. From that moment every matching event starts a run.
The record a run is about
Almost everything in a workflow refers back to the record the trigger fired on. When a contact is created, the record is that contact. When a deal is won, it is that deal. When a tag is applied, it is whatever got tagged. The engine loads that record at the start of the run and makes it available as entity in conditions and templates, and steps that need a record (apply tag, create note, text this person) use it automatically.
Two kinds of run start without a record: scheduled runs and Run now on a workflow that has no contact picked. In those runs a step that needs a record is skipped with a note in the history, not failed. If you want a scheduled workflow to text or email someone, put a literal number or address in the step's To field.
Triggers
When something happens — an event inside Groundbase. Pick one from the list.
| Event | Fires when | Record | Extra values on trigger.payload |
|---|---|---|---|
contact.created | A contact is added by any route: the app, import, a form, the API | the contact | first_name, last_name, email, phone |
contact.updated | A contact is edited | the contact | changed_fields (list of column names), source |
contact.deleted | A contact is deleted | none | contact_id |
company.created / company.updated / company.deleted | Same for companies | the company | name, industry, website; changed_fields on update |
deal.created | A deal is opened | the deal | title, value_cents, stage_id, contact_id, company_id |
deal.updated | A deal is edited | the deal | changed_fields |
deal.stage_changed | A deal moves between stages | the deal | from_stage_id, to_stage_id, to_stage_name, is_won, is_lost, title, contact_id |
deal.won / deal.lost | A deal moves into a stage marked Won or Lost | the deal | same as stage changed |
deal.deleted | A deal is deleted | none | deal_id |
task.created / task.updated / task.completed / task.deleted | Tasks, events and meetings | the task | title, kind, due_at, contact_id, deal_id |
note.created | A note is added to a contact, company or deal | the note | body, entity_type, entity_id, source |
tag.applied / tag.removed | A tag is added to or removed from a contact, company or deal | the tagged record | tag_name, tag_id, entity_type, entity_id |
sms.sent | You send a text (by hand, by a workflow, by a campaign) | the contact | body, to_number, from_number, segments, message_id |
sms.received | A text arrives on one of your numbers | the contact, when the number matched one | body, from_number, to_number, keyword, is_opt_out |
sms.delivered / sms.failed | Twilio reports the outcome of a text | the contact | to_number, twilio_sid; error_message on failure |
email.sent | You send an email from a connected inbox | the contact | to, cc, bcc, subject, from |
call.completed | A call ends | the contact, when the call matched one | direction, status, duration_seconds, from_number, to_number, twilio_call_sid |
voicemail.received | A voicemail lands | the contact, when the caller matched one | from_number, to_number, duration_seconds, recording_url |
invoice.issued / invoice.voided | An invoice is issued or voided | the invoice's contact | number, total, currency, contact_id, company_id |
invoice.paid | An invoice's balance reaches zero. A deposit is payment.recorded, not this | the invoice's contact | total |
payment.recorded | Any payment is recorded against an invoice | none | amount, balance, method |
On a schedule — once at a date and time, or every hour, day or week, in the timezone you pick. The editor shows the next four run times as you edit. There is no record in a scheduled run.
Inbound webhook arrives — an outside system (a form builder, Zapier, your own site) posts to one of your inbound webhooks from Settings → Connections. The record is the contact the webhook created or matched, and the whole posted body is on trigger.payload, so {{trigger.payload.data.first_name}} reads a field from the post.
I run it manually — nothing starts it but the Run now button.
Conditions
A condition is one or more comparisons. Choose all of these (every line must be true) or any of these (one is enough). Each line is a field, a comparison, and usually a value.
Fields you can compare
| Write | Meaning |
|---|---|
entity.<column> | A column on the record the run is about. See the column legend below. |
entity.tags | The record's tags. Use with includes tag. |
contact.<column> | The contact the run is about. On a deal, task or note that is its contact; on a contact it is the record itself. contact.tags and contact.cf.<key> work too. |
cf.<field_key> | One of your custom fields, by its key (Settings → Custom fields). |
trigger.payload.<key> | A value the event carried, from the table above. On tag.applied that is trigger.payload.tag_name. |
trigger.event | The event name, for example deal.won. |
trigger.entity_type / trigger.entity_id | What kind of record the run is about, and its id. |
steps.<step id>.output.<key> | Something an earlier step in the same run produced. See step outputs below. |
A bare column name such as source | Short for entity.source. |
Write trigger.payload.tag_name, not payload.tag_name. The editor refuses the short form because the engine would read it as empty every time and the workflow would silently never run.
Comparisons
| Comparison | Reads as | Needs a value |
|---|---|---|
| equals / not equals | exact match, ignoring case | yes |
| contains / not contains | the value appears anywhere in the field | yes |
| starts with / ends with | ignoring case | yes |
| is set | the field has any value | no |
| is empty / is not empty | the field is blank, or is not | no |
| includes tag | the record carries this tag (use with entity.tags) | yes, a tag name |
| greater than / greater or equal / less than / less or equal | as numbers when both sides are numbers, otherwise as text, which sorts dates correctly | yes |
| older than (days) / newer than (days) | the field is a date more (or less) than this many days ago | yes, a number of days |
Examples: entity.source equals website. entity.phone is set, so a text step cannot fail. entity.tags includes tag VIP. entity.value_cents greater than 500000 for deals over $5,000. entity.last_contacted_at older than (days) 30.
Steps
Each step has a type, its own settings, and an On error choice: Stop workflow (the run fails at this step) or Continue to next step.
| Step | What it does | Settings | Needs a record |
|---|---|---|---|
| Send SMS | Texts through your Twilio number. Honours STOP opt-outs. | Message body (required). To optional: blank texts the run's contact (on a deal, task or note, its contact), or type a number or {{contact.phone}}. | A contact, unless To is filled in |
| Send email | Emails from your connected inbox. | To optional: blank emails the run's contact, or an address or {{contact.email}}. Subject and Body required. | A contact, unless To is filled in |
| Apply tag / Remove tag | Adds or removes a tag on the record. The tag is created if it does not exist. | Tag name (required). Optional entity type and id to target a different record. | Yes |
| Create task | Adds a task, linked to the record: a contact links itself, a deal links the deal and its contact and company. | Title (required), Description, Due in days (0 to 3650, lands at 9 am your time) or Due at (a date), Kind (task, event, meeting), Location. | No, but unlinked without one |
| Create note | Drops a note on a contact, company or deal. | Body (required). | Yes |
| Update field | Changes one native column on the record. | Field and Value. Only these columns: first_name, last_name, email, phone, position, city, source, notes, primary_phone, company_id. | Yes |
| Set custom field | Writes one of your custom fields. | Field key and Value. | Yes |
| Fire webhook | POSTs to an outside URL: Slack, Zapier, your own service. | URL (required), Method (GET, POST, PUT, PATCH, DELETE), Headers, Body. Private addresses and Groundbase's own hosts are refused. | No |
| Add to campaign | Puts the run's contact into an ongoing campaign, at its first step, under the campaign's own rules — skipped if they have unsubscribed or opted out, never added twice. The step fails if the campaign is one-time or not currently sending. | Campaign (an ongoing one). | A contact |
| Wait | Pauses the run. Up to 60 seconds happens inline; anything longer parks the run and it resumes within about a minute of the deadline, up to 30 days. A long wait can also end the run early: tick Replied, Booked a meeting or Opted out, and when the wait is over the run stops instead of continuing if the contact has done that since the run started. A stopped run shows as skipped with the reason. | Amount and unit, plus the stop conditions. A wait inside a branch is capped at 60 seconds; put long waits at the top level. | No |
| Branch | If this, then these steps, else those. | A condition (same rules as above), a then list and an else list. | No |
A workflow may have up to 50 steps and branches three deep.
Templates: putting values into text
Any text field in a step can contain {{...}} placeholders. They are filled in when the step runs. Unknown placeholders come out empty rather than stopping the run.
| Placeholder | Gives you |
|---|---|
{{first_name}}, {{last_name}}, {{full_name}}, {{email}}, {{phone}} | Those columns on the record. In a text or email body, {{company_name}}, {{city}}, {{deal_value}} and {{my_name}} work too, the same as anywhere else in Groundbase. |
{{entity.<column>}} | Any column on the record. {{entity.title}} on a deal, {{entity.position}} on a contact. |
{{cf.<field_key>}} | A custom field. |
{{trigger.event}} | The event name. |
{{trigger.payload.<key>}} | A value the event carried. {{trigger.payload.to_stage_name}} on a stage change; {{trigger.payload.data.email}} from an inbound webhook post. |
{{steps.<step id>.output.<key>}} | Something an earlier step produced. Give the step an id in the editor so the reference survives edits. |
Press Variable beside any field to insert one from a list instead of typing it.
Column legend: what entity. can reach
| Record | Columns |
|---|---|
| Contact | first_name, last_name, full_name, email, phone, phone_secondary, primary_phone, company_id, position, city, source, notes, last_contacted_at, created_at, updated_at, plus tags |
| Company | name, industry, website, city, state, country, notes, created_at, updated_at, plus tags |
| Deal | title, value_cents (whole cents: $1,500 is 150000), stage_id, contact_id, company_id, close_date, notes, created_at, updated_at, plus tags |
| Task | title, description, due_at, end_at, kind, location, priority, contact_id, company_id, deal_id, completed_at, created_at, updated_at |
Step outputs: what steps.<id>.output. can reach
| Step | Output keys |
|---|---|
| Send SMS | message_id, segments, status, to, contact_id |
| Send email | message_id, rfc_message_id, to, subject, contact_id |
| Apply tag / Remove tag | tag_name, entity_type, entity_id |
| Create task | task_id, title, due_at, contact_id |
| Create note | note_id, entity_type, entity_id, body |
| Update field / Set custom field | entity_type, entity_id, field or field_key, value |
| Fire webhook | url, method, status_code, response_body |
| Add to campaign | campaign_id, contact_id, queued, already, skipped |
| Branch | passed (true or false), ran |
| Wait | delay_seconds, resumed_from (inline or cron) |
Running one by hand
Run now on a workflow's row starts it immediately. For workflows that react to a record event it asks you to pick a contact first, and that contact becomes the record. Two things to know:
- Run now has no event behind it, so a condition on
trigger.payload.<key>is always false and the run is skipped. To test a tag-triggered workflow, apply the tag for real. - It runs every step for real. A Send SMS step sends the text.
Reading the run history
Open a row's chevron to see its runs, newest first. Each run has a status and one line per step.
| Run status | Meaning |
|---|---|
| Completed | Every step ran or was skipped by design. |
| Skipped | The ONLY IF condition was false for this record. Nothing ran. |
| Failed | A step errored with On error set to Stop. The line says which step and why. |
| Waiting | The run is parked on a Wait step. The row shows N waiting. |
Step lines read ok with what happened ("created task … linked to deal", "applied hot to contact Jane Doe"), skipped with the reason, or failed with the error.
Pausing, editing and deleting
- Pause stops new runs and holds any runs that are waiting on a delay. They stay parked until you resume, then continue.
- Editing a workflow with waiting runs shows a warning. Removing or reordering the step a run is waiting on makes that run fail when it resumes. Pause first if you would rather it did not run at all.
- Delete stops it immediately and removes its history. Waiting runs are closed as failed.
- Paused by the engine · N failed runs on a row means it failed twenty times in a row and was switched off for you. Open the history, fix the cause, then Resume. If the cause is still there it will pause again after twenty more.
Ask the assistant instead
If you use Groundbase with Claude or ChatGPT through AI integrations, you can say what you want in a sentence ("when a deal is won, create a task to send the welcome kit") and the assistant will build it. It can also read the run history and tell you why a workflow skipped or failed.