Webhooks
Competiflow can POST structured JSON to your HTTPS endpoint when a competitor change is detected or when a digest window closes. Configure a webhook URL in Workspace settings → Notifications.
See also Notifications for email and Slack delivery.
Envelope
Every delivery uses the same top-level shape:
{
"schema_version": "v1",
"id": "wh_123",
"idempotency_key": "…",
"event": "change.detected",
"sent_at": "2026-07-18T19:58:44.000Z",
"data": {}
}
| Field | Description |
|---|---|
schema_version |
Contract version (v1) |
id |
Unique delivery id — store this to dedupe retries |
idempotency_key |
Stable key for the logical delivery (same across retries) |
event |
Event type (see below) |
sent_at |
UTC timestamp when Competiflow sent the webhook |
data |
Event-specific payload |
Events
| Event | When |
|---|---|
change.detected |
Realtime route fired for a single change |
digest.ready |
Digest window flushed (Slack/webhook digest mode) |
test.ping |
You clicked Test in notification settings |
Signing
If you set a signing secret, Competiflow sends:
X-Competiflow-Timestamp: 1721335124
X-Competiflow-Signature: sha256=<hex>
The signed payload is "{timestamp}.{raw_json_body}" using HMAC-SHA256 and your secret.
expected = OpenSSL::HMAC.hexdigest("SHA256", secret, "#{timestamp}.#{raw_body}")
valid = Rack::Utils.secure_compare("sha256=#{expected}", signature_header)
Reject requests when the timestamp is older than five minutes.
Dedupe and retries
Failed deliveries retry up to five times. Treat duplicate id or idempotency_key values as already processed and respond 2xx without side effects.
change.detected
{
"schema_version": "v1",
"id": "wh_456",
"idempotency_key": "abc…",
"event": "change.detected",
"sent_at": "2026-07-18T19:58:44.000Z",
"data": {
"id": 133489,
"workspace_id": 12,
"workspace_name": "Acme",
"competitor_id": 24,
"competitor": "apify.com",
"monitor_id": 88,
"monitor_type": "homepage",
"severity": "medium",
"summary": "The competitor updated their headline…",
"recommended_action": "Evaluate our messaging…",
"review_status": "unreviewed",
"category": "positioning",
"direction": "neutral",
"delta": { "added": 0, "removed": 0, "modified": 2 },
"field_diffs": [
{
"op": "modified",
"path": "headline",
"tier": "alertable",
"change_type": "headline_changed",
"old": "53,432 tools for your AI",
"new": "53,502 tools to automate your business"
}
],
"evidence": {
"captured_at": "2026-07-18T19:58:41Z",
"previous_captured_at": "2026-07-18T16:47:41Z",
"diffs": [
{ "op": "modified", "path": "headline", "tier": "alertable", "change_type": "headline_changed" }
]
},
"source_url": "https://apify.com",
"links": { "self": "https://api.competiflow.com/v1/changes/133489" },
"raw_diff_available": true,
"created_at": "2026-07-18T19:58:41.982Z"
}
}
Field locations
- Full before/after values:
data.field_diffs - Evidence index (no duplicate values):
data.evidence.diffs - Fetch more via
data.links.selforGET /v1/changes/:id?include=raw_diff
digest.ready
{
"schema_version": "v1",
"id": "wh_a1b2…",
"idempotency_key": "digest-7-12-1d-1721332800",
"event": "digest.ready",
"sent_at": "2026-07-18T20:00:00.000Z",
"data": {
"workspace_id": 12,
"workspace_name": "Acme",
"digest": {
"period": "1d",
"generated_at": "2026-07-18T20:00:00.000Z",
"needs_review": {
"count": 3,
"max_severity": "high",
"items": [
{
"change_id": 901,
"competitor_id": 7,
"competitor": "Example Co",
"homepage_url": "https://example.com",
"logo_url": "https://…",
"monitor_type": "pricing",
"source_url": "https://example.com/pricing",
"summary": "Pro plan dropped from $99 to $79/mo",
"severity": "high",
"recommended_action": "Review positioning",
"category": "pricing",
"direction": "retreated",
"delta": { "added": 0, "removed": 0, "modified": 1 },
"review_status": "unreviewed",
"created_at": "2026-07-18T19:00:00.000Z"
}
]
},
"this_week": {
"bullets": [{ "text": "Example Co cut Pro pricing", "change_ids": [901] }]
},
"whos_moving": [],
"top_opportunities": [],
"coverage": { "monitors_total": 18, "monitors_healthy": 17, "monitors_failing": 0, "failing_monitor_ids": [] },
"consumption": { "pending": 3, "reviewed_in_period": 1 }
}
}
}
Notification digest vs API digest
Both use the same needs_review.items[] shape (see Digest). They differ in scope:
| Aspect | digest.ready webhook / email digest |
GET /v1/workspaces/:id/digest |
|---|---|---|
| Changes included | Buffered in the notification flush window | Full workspace period |
needs_review filter |
All unreviewed in buffer | Unreviewed with severity ≥ high |
this_week.bullets |
First buffered summaries | OpenAI synthesis (or DB fallback) |
whos_moving.sparkline |
Single-bucket count | Daily buckets over period |
test.ping
Sent when you click Test in notification settings. Each test uses a unique wh_test_… delivery id so repeated tests are not deduped by your receiver.
{
"schema_version": "v1",
"id": "wh_test_a1b2c3d4e5f67890",
"idempotency_key": "wh_test_a1b2c3d4e5f67890",
"event": "test.ping",
"sent_at": "2026-07-18T20:00:00.000Z",
"data": { "message": "Competiflow webhook test" }
}