{"schema":"https://policywatcher.online/schemas/webhook-verification-kit/v1","kitVersion":"1.0.0","status":"readiness-contract","deliveryAvailable":false,"algorithm":"HMAC-SHA256","signatureVersion":"v1","signedPayloadFormat":"{unix_timestamp}.{raw_request_body}","headers":{"eventId":"PolicyWatcher-Event-Id","timestamp":"PolicyWatcher-Timestamp","signature":"PolicyWatcher-Signature"},"candidateTimestampToleranceSeconds":300,"testVector":{"id":"policywatcher-hmac-sha256-v1-public-vector","verificationMode":"signature-compatibility-only","secretClassification":"public-test-only","secret":"whsec_test_policywatcher_public_vector_v1","timestamp":1785326400,"freshnessReferenceSeconds":1785326400,"eventId":"pwe_7d94a2e87c1f1af16a93","payload":"{\"eventId\":\"pwe_7d94a2e87c1f1af16a93\",\"eventType\":\"policy.change.published\",\"schemaVersion\":\"1.0.0\",\"occurredAt\":\"2026-07-29T12:00:00.000Z\",\"subject\":{\"changeId\":\"11111111-1111-4111-8111-111111111111\"},\"links\":{\"change\":\"https://policywatcher.online/change/11111111-1111-4111-8111-111111111111\",\"evidence\":\"https://policywatcher.online/evidence/11111111-1111-4111-8111-111111111111\"}}","signedPayload":"1785326400.{\"eventId\":\"pwe_7d94a2e87c1f1af16a93\",\"eventType\":\"policy.change.published\",\"schemaVersion\":\"1.0.0\",\"occurredAt\":\"2026-07-29T12:00:00.000Z\",\"subject\":{\"changeId\":\"11111111-1111-4111-8111-111111111111\"},\"links\":{\"change\":\"https://policywatcher.online/change/11111111-1111-4111-8111-111111111111\",\"evidence\":\"https://policywatcher.online/evidence/11111111-1111-4111-8111-111111111111\"}}","signatureHeader":"v1=72b5cde210f2adf69d92fcb65a9a236a327ec67d5032f6ef9277485cbec964ba"},"testVectorInstructions":["Use the exact public test secret, timestamp and raw payload bytes shown in the vector.","For this static vector only, evaluate freshness with the verification clock fixed at 1785326400.","Do not disable timestamp freshness or replay protection for production deliveries."],"conformanceSuite":{"href":"/api/v1/webhook-conformance-suite","schema":"https://policywatcher.online/schemas/webhook-conformance-suite/v1","caseCount":8},"receiverRequirements":["Read the exact raw request bytes before JSON parsing or body transformation.","Resolve a tenant-owned secret from a managed secret store; never use the public test secret.","Reject timestamps outside the configured tolerance; the candidate default is 300 seconds.","Compare signatures with a constant-time primitive.","Store accepted event IDs or nonces for a bounded replay-protection window.","Support overlapping active secrets during controlled key rotation.","Record bounded delivery outcomes without logging secrets or raw private payloads."],"examples":{"node":"import { createHmac, timingSafeEqual } from 'node:crypto';\n\nconst timestamp = request.headers.get('PolicyWatcher-Timestamp');\nconst signature = request.headers.get('PolicyWatcher-Signature');\nconst eventId = request.headers.get('PolicyWatcher-Event-Id');\nconst rawBody = Buffer.from(await request.arrayBuffer());\n\nif (!/^\\d+$/.test(timestamp ?? '') || !/^v1=[a-f0-9]{64}$/.test(signature ?? '')) {\n  throw new Error('Invalid webhook headers');\n}\n\nconst age = Math.abs(Math.floor(Date.now() / 1000) - Number(timestamp));\nif (age > 300) throw new Error('Stale webhook timestamp');\n\nconst expected = createHmac('sha256', process.env.POLICYWATCHER_WEBHOOK_SECRET)\n  .update(`${timestamp}.`, 'utf8')\n  .update(rawBody)\n  .digest('hex');\nconst supplied = signature.slice(3);\n\nif (!timingSafeEqual(Buffer.from(expected, 'hex'), Buffer.from(supplied, 'hex'))) {\n  throw new Error('Invalid webhook signature');\n}\n\n// Check eventId in a bounded replay store before processing.","python":"import hashlib\nimport hmac\nimport os\nimport re\nimport time\n\ntimestamp = request.headers[\"PolicyWatcher-Timestamp\"]\nsignature = request.headers[\"PolicyWatcher-Signature\"]\nevent_id = request.headers[\"PolicyWatcher-Event-Id\"]\nraw_body = request.get_data(cache=False, as_text=False)\n\nif not timestamp.isdigit() or re.fullmatch(r\"v1=[a-f0-9]{64}\", signature) is None:\n    raise ValueError(\"Invalid webhook headers\")\nif abs(int(time.time()) - int(timestamp)) > 300:\n    raise ValueError(\"Stale webhook timestamp\")\n\nmessage = timestamp.encode() + b\".\" + raw_body\nexpected = hmac.new(\n    os.environ[\"POLICYWATCHER_WEBHOOK_SECRET\"].encode(),\n    message,\n    hashlib.sha256,\n).hexdigest()\n\nif not hmac.compare_digest(expected, signature[3:]):\n    raise ValueError(\"Invalid webhook signature\")\n\n# Check event_id in a bounded replay store before processing."},"boundary":"This public kit defines a candidate receiver-verification contract and deterministic test vector. A separate deployment-configured pilot can send signed public change events to allowlisted HTTPS destinations with a persistent outbox and bounded retries. PolicyWatcher does not provide public subscriptions, endpoint self-service, automatic key rotation, guaranteed delivery or an SLA."}