PPactDocs
CRM

Playbook detail

Inspect and edit a single trigger-based automation playbook — its conditions, actions, dry-run tester, and execution log.

Playbook detail

A playbook is a trigger-based automation rule: it watches one trigger_event on the account timeline, evaluates a list of conditions when that event fires, and — if every condition passes — runs an ordered list of actions. Playbooks are Pact's answer to Salesforce Process Builder / Flow and Marketo Smart Campaigns, but stored as plain JSON rows and evaluated in-process by core.playbook.PlaybookEngine so they are diffable, peer-reviewable, and unit-testable.

The detail page is the single-record view backing GET /v1/playbooks/{id}. It exposes four things: the rule definition, a dry-run tester, the enable/disable toggle, and the execution log.

Marketing module + RBAC

Every playbook route is gated by Depends(verify_api_key) and Depends(require_module("marketing")), and is tenant-scoped — the record is loaded with WHERE id = :id AND tenant_id = :tid, so a leaked id cannot be read across tenants.

Anatomy of a playbook

A playbook row (playbooks table) carries a name, description, trigger_event, a JSON conditions_json blob, a JSON actions_json blob, and two behavior flags. The API expands the JSON blobs into conditions and actions arrays on read.

json
{
  "id": "b3f1…",
  "name": "Escalate enterprise closed-won",
  "trigger_event": "deal.closed_won",
  "conditions": [
    { "field": "revenue_usd", "op": "gte", "value": 100000 }
  ],
  "actions": [
    { "action_type": "enroll_sequence", "params": { "sequence_id": 12 } },
    { "action_type": "notify_webhook", "params": { "url": "https://hooks…" } }
  ],
  "enabled": true,
  "run_once_per_account": true
}

Conditions are field op value triples. field is a dot-path into the evaluation context (icp_score, revenue_usd, _event.event_type, _event.metadata.job_id). The operator set is fixed in core.playbook._OPS: eq, ne, gt, gte, lt, lte, contains, not_contains, exists, not_exists, in, not_in. All conditions must pass (logical AND) for actions to fire.

Actions run in order. Four action_type values are implemented:

  • enqueue_job — enqueue a worker job (default task enrich_company), with the account id injected into the payload.
  • enroll_sequence — enqueue an enroll_sequence job for the given sequence_id.
  • set_stage — write crm_stage on the company and record a stage-transition activity (changed_by="playbook").
  • notify_webhook — enqueue a fire_webhook job carrying the event and account id.

run_once_per_account

Setting run_once_per_account: true makes the playbook idempotent per account — it will not re-fire for an account it has already fired for, which is the safe default for one-shot escalations. Unknown action_type values are silently ignored, so the engine is forward-compatible with actions a newer build understands.

Editing

PATCH /v1/playbooks/{id} accepts a partial body — any of name, description, trigger_event, conditions, actions, enabled, run_once_per_account. Only the supplied fields are written; the response is the full re-read record. Toggling enabled is the pause switch — a disabled playbook stays in the list but never fires.

Dry-run before you ship

POST /v1/playbooks/{id}/test evaluates the rule against a sample event and account row without enqueuing any jobs or writing any rows. It returns per-condition pass/fail, whether the event type matched the trigger, and which actions would fire:

bash
curl -X POST /v1/playbooks/$ID/test \
  -H "Authorization: Bearer $KEY" \
  -d '{
    "event": { "event_type": "deal.closed_won", "metadata": {} },
    "account_row": { "revenue_usd": 250000, "crm_stage": "customer" }
  }'
json
{
  "trigger_event": "deal.closed_won",
  "event_type_matches": true,
  "conditions": [{ "field": "revenue_usd", "op": "gte", "value": 100000, "passed": true }],
  "all_conditions_pass": true,
  "actions_would_fire": [ … ]
}

Execution log

GET /v1/playbooks/{id}/runs returns rows from playbook_run_log — one per fired action, with account_id, action_type, params_json, status, error, and fired_at. This is the audit trail that replaces Salesforce Debug Logs: every firing, successful or failed, is recorded here.