Best Practices
Automation — Best Practices
The recommended way to build reliable, correct workflows.
✓ Start narrow, then widen
Pick the tightest trigger for the intent — status_changed to one value beats
field_changed on a broad set of fields. Add filter_config conditions so the
action only runs on the records you mean. A skipped execution is cheap; a wrong
send is not.
✓ Validate before you activate
Call POST /workflow/meta/validate-session-link (message triggers) and
POST /workflow/:slug/test (dry run) before flipping the toggle. Activation
enforces the same session-link gate anyway — catch it in the builder, not with a
failed live run.
✓ Reference everything by slug
object_slug, session_slug, field_slug, status_field_slug are stable across
environments. Never build a config around a numeric id — it breaks across
dev/stage/prod and data migrations.
✓ Let variables do the work
Personalize with {{field_slug}}, trigger diff ({{old_value}}, {{new_value}},
{{admin_name}}), system dates ({{today}}, {{TODAY + 3}}), and message context
({{message_body}}, {{sender_name}}). Pull the palette from
GET /workflow/meta/variables/:triggerType.
✓ Design actions to be idempotent
Runs can retry (Bull, provider webhook retries). An update_field that sets an
absolute value is safe to run twice; a webhook receiver should de-dupe on
record_slug + timestamp. Don't assume exactly-once.
✓ Verify outbound webhook signatures
Always check X-Workflow-Signature (HMAC-SHA256 of the raw body with your shared
secret) before trusting a send_webhook payload. Never target private/local URLs —
the engine blocks them.
✓ Watch runs, don't poll
Subscribe to /workflow/events for execution.completed/failed/skipped and
stats.updated. Fall back to GET /workflow/:slug/executions for history, not as a
live poll.
✓ Mind the limits
Per workspace: 50 workflows, ≤ 20 filter conditions, ≤ 10 field updates per action, 4096-char message templates. Workflow runs also draw on the workspace's monthly execution allowance — exhausted allowance skips new runs.
✓ Pace bulk sends
For scheduled reminder campaigns on WhatsApp, use the trigger's send_guard
(daily cap, random gaps, rest breaks, warm-up) so a fresh number isn't blasted and
flagged. Matches roll over across ticks instead of firing all at once.
✗ Don't rely on a workflow re-triggering itself
An action's own CRM write is tagged sourceType: "automation" and won't re-fire
the engine (loop prevention). Model multi-step logic with steps[] (Action / Delay
/ Condition / Approval), not by chaining separate workflows through side effects.
✗ Don't reach past the pinned connection
When extending the engine, all DB access goes through repoProvider and every
processor handler is @TenantScoped(). A raw DataSource bypasses RLS on
hyper-tenant and silently reads/writes zero rows. See
Backend Integration.