dotb_ai-platform/docs/integrations/lead-scoring-webhook-v1.md
2026-09-14 09:57:12 +07:00

6.8 KiB

title, description, status, tags
title description status tags
Lead Scoring Webhook v1 Contract profile for terminal Lead Scoring webhook delivery and DotB-Omni ingestion. draft
lead-scoring
webhook
integration

Lead Scoring terminal webhook v1

Authority and release

This document defines delivery semantics and security handling around the executable contract pack. Exact JSON shapes, constraints, golden payload bytes, and signature vectors are generated from Pydantic models and committed at contracts/lead-scoring-webhook/v1/.

Generate the pack with uv run generate-webhook-contract; verify it with uv run generate-webhook-contract --check. contract.json identifies the contract pack version and event version. DotB-Omni must pin the pack by AI Platform Git commit when implementation begins and run its consumer tests against those committed files. Do not recreate envelope schemas, payload fixtures, or signature vectors in Omni.

This pack is a pre-release baseline. The approved Customer Pilot specification remains the authority for product behavior and ownership. Ticket 08 owns the future dispatcher; this contract does not implement a dispatcher or receiver.

Delivery

Lead Scoring sends POST /api/v1/internal/lead-scoring/events only to a deployment-managed registered DotB-Omni subscriber. The endpoint is versioned by its /api/v1/ path and is never accepted from a scoring-run request. Each committed terminal run emits lead_scoring.run.terminal event version 1.0.0 with one of these statuses:

  • completed
  • review_required
  • failed, with machine-readable error_code and retryable fields

The generated worker_interrupted.json fixture is the normative failed-outcome example for an in-memory worker interruption (error_code=worker_interrupted, retryable=true). Other normalized failed outcomes can be added as versioned fixtures when their producer path is enabled. Events contain no V-Key, raw snapshot, raw conversation evidence, or unneeded PII.

Delivery is at least once. A retry retains the event identity but creates a fresh Unix timestamp and signature for that delivery attempt. Ingestion success means only that Omni accepted and recorded the event decision; it does not mean EMS Marker Block writeback has completed.

Signed request

Every delivery has these headers:

Header Value
Content-Type application/json
X-DotB-Webhook-Id The body event_id UUID
X-DotB-Webhook-Key-Id Deployment-managed signing-key identifier
X-DotB-Webhook-Timestamp Decimal Unix timestamp used for signing
X-DotB-Webhook-Signature v1=<lowercase HMAC-SHA256 hex digest>

The signature input is the UTF-8 byte sequence <event_id>.<unix_timestamp>.<raw_request_body>. The receiver first rejects timestamps outside 300 seconds before or after its current server clock, then verifies the signature against the unparsed request bytes before JSON parsing. It must compare signatures in constant time and confirm that the header event ID equals the payload event ID. A retry outside the original freshness window is valid only when the dispatcher signs that attempt with a fresh timestamp; event idempotency remains keyed by the unchanged event identity.

signature-vectors.json contains deterministic test-only secrets and expected signatures for every fixture. Those values are not production credentials and must not be deployed as webhook secrets. Production signing keys live only in the deployment secret store and are never written to source, fixtures, logs, or response bodies.

X-DotB-Webhook-Key-Id supports rotation. Deployment configuration selects the active key for sends; the receiver may accept the active and immediately previous configured key during an explicitly bounded rotation window. Unknown key IDs or invalid signatures are authentication failures, must be audited without the secret or raw body, and are not acknowledged as successful deliveries.

Receiver acknowledgement and ordering

For an authenticated, schema-valid delivery, DotB-Omni returns HTTP 200 and the body validated by webhook-ingest-response.schema.json:

  • accepted: event is eligible for asynchronous writeback handling.
  • duplicate: the same run is already reflected.
  • stale: a newer applicable run is already reflected.
  • ordering_conflict: another run has the same ordering key and run_created_at.

All four are terminal ingestion acknowledgements. Invalid JSON or schema validation is HTTP 400. Invalid webhook authentication is HTTP 401. The future dispatcher must treat these non-2xx results as observable terminal delivery failures rather than success.

When the webhook signature and payload schema are valid but the receiver cannot resolve the tenant destination, the receiver must return the same response for all of these cases:

  • tenant_id is not registered.
  • Tenant-to-account mapping is invalid.
  • The mapped account has been deleted.
HTTP/1.1 404 Not Found
Content-Type: application/json
{
  "error_code": "subscriber_tenant_unavailable",
  "detail": "Webhook destination is not available"
}

The executable error shape is webhook-ingest-error-response.schema.json, and fixtures/tenant-unavailable.json is the shared golden response body.

The response must not disclose the tenant ID, account ID, mapping state, or internal reason. The dispatcher classifies this response as terminal receiver_client_error, moves the event to the dead-letter state, and does not schedule an automatic retry. After provisioning or mapping repair, recovery uses operational DLQ replay. The receiver may retain a separate sanitized internal audit reason for operations, but that reason is not part of the outward response.

Transient receiver failures use non-2xx responses such as HTTP 429 or 5xx and are eligible for Ticket 08's bounded retry and DLQ policy.

For the same (tenant_id, subject_ref, scoring_config_version), Omni compares run_created_at, never occurred_at. A same-run duplicate and stale event are no-ops. Two different run IDs with equal run_created_at are an ordering conflict and must not overwrite current state. The receiver must preserve this decision and return the matching acknowledgement even when delivery is replayed.

Consumer implementation boundary

DotB-Omni owns signature verification, schema validation, tenant-scoped ordering, identity-to-EMS resolution, and Marker Block writeback. It resolves the payload tenant_id through a deployment-managed tenant-to-account mapping or allowlist and scopes every lookup and writeback to that mapped account; the payload value is never used as a direct record selector. Its background writeback job handles EMS retry classification separately from webhook acknowledgement. AI Platform owns outbox persistence and future dispatch, including subscriber provisioning and DLQ visibility. Neither side should infer transport behavior from free-text error messages.