Dbt Test Gate Before Hightouch Activation with PagerDuty Alert
Build dbt models, gate on dbt test, then activate a Hightouch sync only when tests pass, paging PagerDuty when they fail.
Trigger and audit Hightouch reverse ETL syncs on a schedule, alert Slack on failed rows, and optionally retry and verify the fix.
A reverse ETL sync that silently drops rows is worse than one that fails outright, the
destination (a CRM, an ad platform, a support tool) looks current, but the sales rep is
calling a lead with last week's lifecycle stage. Hightouch reports exactly how many rows
succeeded and failed on every run, status, failedRows, completionRatio, but nothing
reads that report unless you build it. This blueprint turns a Hightouch sync into a Day 2
governance gate: it triggers the sync, classifies the real outcome, alerts Slack with the
exact row counts, and can retry once before anyone has to go looking.
probe_sync (io.kestra.plugin.hightouch.Sync) triggers the sync by sync_id and
waits for a terminal status, polling every second up to max_wait. allowFailure: true
matters here: Hightouch's own task throws an exception rather than returning output for
a FAILED, CANCELLED or INTERRUPTED run, or if max_wait is exceeded, so without
allowFailure the flow would stop before it could classify or alert on exactly the
case that matters most.classify_sync_status (io.kestra.plugin.core.debug.Return) reads
outputs.probe_sync.metadata.status and the three failedRows counts (addedCount,
changedCount, removedCount). PROBE_FAILED when probe_sync produced no output at
all (guarded with is not defined), DEGRADED when it succeeded but the total failed
rows exceed failed_rows_warning_threshold or the status is not exactly SUCCESS (for
example WARNING or COMPLETED_WITH_ERRORS), HEALTHY otherwise.route_by_sync_status (io.kestra.plugin.core.flow.Switch) branches on that status:log_healthy logs the status and completionRatio.SlackIncomingWebhook task alerts with the exact status and the three
failed row counts.SlackIncomingWebhook task alerts that the sync produced no
result, with the plain-language explanation of why Hightouch's own task behaves that
way for a failed or cancelled run.remediate_gate (io.kestra.plugin.core.flow.If) runs only when the classification was
not HEALTHY and auto_retry is true and dry_run is false:retry_sync triggers the same sync again, with fullResynchronization set from
retry_mode.reclassify_sync_status runs the identical classification against the retry's output.verify_retry (io.kestra.plugin.core.flow.If) fails the flow with
io.kestra.plugin.core.execution.Fail if the retry is still not HEALTHY, so a failed
retry is never silently reported as a fix.log_audit_summary always runs last and logs the final classification.errors block posts to Slack if the flow itself fails outside the gate logic above.dry_run: true ships enabled, nothing retries until you
explicitly turn both auto_retry on and dry_run off.io.kestra.plugin.hightouch.Sync, its metadata output, and the exception-on-failure
behavior that is easy to miss on a first read of the plugin.Hightouch's own scheduler can retrigger a sync, but it has no concept of an allow-listed
failure threshold, a classification, or an alert that explains what actually went wrong in
plain numbers. Kestra adds the schedule, the DEGRADED versus PROBE_FAILED distinction,
the Slack alert built from Hightouch's own reported counts, the gated retry, and the
verification that the retry worked, all as one declarative YAML file you can version and
review like code.
HIGHTOUCH_API_TOKEN: API bearer token used by probe_sync and retry_sync.
SLACK_WEBHOOK_URL: Slack incoming webhook URL used by the alert tasks and the errors
block.
In Kestra OSS, set both as base64-encoded environment variables:
SECRET_HIGHTOUCH_API_TOKEN, SECRET_SLACK_WEBHOOK_URL.
| Input | Type | Default | Purpose |
|---|---|---|---|
sync_id |
INT | 1127166 |
Numeric ID of the Hightouch sync to trigger and monitor |
full_resync |
BOOL | false |
When true, forces a full resynchronization of all records |
max_wait |
STRING | PT5M |
Maximum wait duration before timeout |
failed_rows_warning_threshold |
INT | 0 |
Failed row count threshold before marking run DEGRADED |
retry_mode |
SELECT | incremental |
Resync mode for retry (incremental or full) |
auto_retry |
BOOL | false |
When true with dry_run: false, triggers a single retry |
dry_run |
BOOL | true |
Must be false for the retry execution to run |
HIGHTOUCH_API_TOKEN and SLACK_WEBHOOK_URL secrets.log_audit_summary for the result.DEGRADED or PROBE_FAILED and a retry is the right call, set
auto_retry: true and dry_run: false and run it again.schedule_every_6_hours trigger, which always ships in safe mode.failed_rows_warning_threshold to 0 on a sync where every row must land, or raise
it on a sync where a small drop rate is expected and tolerated.probe_sync for a different sync_id and reuse the same classification
logic to monitor several syncs from one flow.outputs.probe_sync.metadata.querySize and alert separately when it grows far past
its usual size, a sign the source query itself has drifted.HEALTHY that writes completionRatio to a Kestra KV
entry, to track sync health over time instead of only per run.Sync task does not return a FAILED, CANCELLED or INTERRUPTED run
as output with that status, it throws an exception instead. Without allowFailure: true
on probe_sync and retry_sync, the flow stops before classify_sync_status ever runs.max_wait also throws, with the same effect, a sync that is simply slow
looks identical to one that failed outright. Raise max_wait before assuming every
PROBE_FAILED is a real failure.completionRatio can be high while failedRows is still non-zero, most of the batch
landed but not all of it, which is exactly the case failed_rows_warning_threshold
exists to catch even when Hightouch itself reports status: SUCCESS.