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 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:
completedreview_requiredfailed, with machine-readableerror_codeandretryablefields
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 andrun_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_idis 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.