{
  "schema_version": "1",
  "generator": "backend/scripts/generate_webhook_test_vectors.py",
  "wire_format_doc": "docs/integration/consumer-integration.md (see \u00a75.5 Signing Scheme & Verification)",
  "encoding_notes": {
    "body_b64": "Standard base64 (RFC 4648) of raw HTTP body bytes.",
    "signed_input": "Receivers reconstruct via: f'{timestamp_unix}.'.encode('ascii') + base64.b64decode(body_b64)",
    "verification": "hmac.compare_digest(hmac.new(signing_secret.encode(), signed_input, hashlib.sha256).hexdigest(), v1_value_parsed_from_signature_header)",
    "freshness_window_seconds": 300
  },
  "cases": [
    {
      "case_name": "valid_happy_path",
      "description": "Canonical signed delivery: well-formed JSON body, signature produced by the runtime helper for the fixed timestamp.  Receivers MUST accept after validating the freshness window (test harness should mock wall-clock to within 5 min of timestamp_unix).",
      "body_b64": "eyJ3ZWJob29rX2lkIjogIjEyMzQ1Njc4LTEyMzQtNTY3OC0xMjM0LTU2NzgxMjM0NTY3OCIsICJzY2hlbWFfdmVyc2lvbiI6ICIxIiwgImV2ZW50IjogeyJldmVudF9pZCI6ICIxMTExMTExMS0yMjIyLTMzMzMtNDQ0NC01NTU1NTU1NTU1NTUiLCAiZXZlbnRfdHlwZSI6ICJnZW9mZW5jZS5lbnRlciIsICJkZXZpY2UiOiB7ImlkIjogImRkZGRkZGRkLTExMTEtMjIyMi0zMzMzLTQ0NDQ0NDQ0NDQ0NCIsICJleHRlcm5hbF9pZCI6ICJ0cnVjay00MiJ9LCAiZ2VvZmVuY2UiOiB7ImlkIjogIjk5OTk5OTk5LTg4ODgtNzc3Ny02NjY2LTU1NTU1NTU1NTU1NSIsICJuYW1lIjogIk9yaWdpbiBZYXJkIn0sICJsb2NhdGlvbiI6IHsibGF0aXR1ZGUiOiA0NS41MTUyLCAibG9uZ2l0dWRlIjogLTEyMi42Nzg0LCAiYWNjdXJhY3lfbWV0ZXJzIjogNS4wfSwgInRyaWdnZXJlZF9hdCI6ICIyMDI2LTA0LTIzVDE3OjQ1OjAwKzAwOjAwIiwgInNvdXJjZV9waW5nX2lkIjogIjAwMDAwMDAwLWFhYWEtYmJiYi1jY2NjLTExMTExMTExMTExMSIsICJtZXRhZGF0YSI6IG51bGx9LCAidGVuYW50X2lkIjogImFhYWFhYWFhLWJiYmItY2NjYy1kZGRkLWVlZWVlZWVlZWVlZSIsICJhdHRlbXB0ZWRfYXQiOiAiMjAyNi0wNC0yM1QxNzo0NTowNSswMDowMCJ9",
      "body_text": "{\"webhook_id\": \"12345678-1234-5678-1234-567812345678\", \"schema_version\": \"1\", \"event\": {\"event_id\": \"11111111-2222-3333-4444-555555555555\", \"event_type\": \"geofence.enter\", \"device\": {\"id\": \"dddddddd-1111-2222-3333-444444444444\", \"external_id\": \"truck-42\"}, \"geofence\": {\"id\": \"99999999-8888-7777-6666-555555555555\", \"name\": \"Origin Yard\"}, \"location\": {\"latitude\": 45.5152, \"longitude\": -122.6784, \"accuracy_meters\": 5.0}, \"triggered_at\": \"2026-04-23T17:45:00+00:00\", \"source_ping_id\": \"00000000-aaaa-bbbb-cccc-111111111111\", \"metadata\": null}, \"tenant_id\": \"aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee\", \"attempted_at\": \"2026-04-23T17:45:05+00:00\"}",
      "signing_secret": "openfence-test-vectors-secret-3",
      "timestamp_unix": 1714780000,
      "signature_header": "t=1714780000,v1=a68792632078df04344eddc605dda3d79b04e54f4be371b9806fd92a75398095",
      "timestamp_header": "1714780000",
      "expected_outcome": "accepted",
      "expected_event_id": "11111111-2222-3333-4444-555555555555"
    },
    {
      "case_name": "valid_second_payload",
      "description": "Second valid delivery with a different event_id and event_type.  Distinct from valid_happy_path so receiver dedup tests can verify two different events both accept.",
      "body_b64": "eyJ3ZWJob29rX2lkIjogIjEyMzQ1Njc4LTEyMzQtNTY3OC0xMjM0LTU2NzgxMjM0NTY3OCIsICJzY2hlbWFfdmVyc2lvbiI6ICIxIiwgImV2ZW50IjogeyJldmVudF9pZCI6ICI2NjY2NjY2Ni03Nzc3LTg4ODgtOTk5OS1hYWFhYWFhYWFhYWEiLCAiZXZlbnRfdHlwZSI6ICJnZW9mZW5jZS5leGl0IiwgImRldmljZSI6IHsiaWQiOiAiZGRkZGRkZGQtMTExMS0yMjIyLTMzMzMtNDQ0NDQ0NDQ0NDQ0IiwgImV4dGVybmFsX2lkIjogInRydWNrLTQyIn0sICJnZW9mZW5jZSI6IHsiaWQiOiAiOTk5OTk5OTktODg4OC03Nzc3LTY2NjYtNTU1NTU1NTU1NTU1IiwgIm5hbWUiOiAiT3JpZ2luIFlhcmQifSwgImxvY2F0aW9uIjogeyJsYXRpdHVkZSI6IDQ1LjUxNTIsICJsb25naXR1ZGUiOiAtMTIyLjY3ODQsICJhY2N1cmFjeV9tZXRlcnMiOiA1LjB9LCAidHJpZ2dlcmVkX2F0IjogIjIwMjYtMDQtMjNUMTg6MzA6MDArMDA6MDAiLCAic291cmNlX3BpbmdfaWQiOiAiMDAwMDAwMDAtYWFhYS1iYmJiLWNjY2MtMTExMTExMTExMTExIiwgIm1ldGFkYXRhIjogbnVsbH0sICJ0ZW5hbnRfaWQiOiAiYWFhYWFhYWEtYmJiYi1jY2NjLWRkZGQtZWVlZWVlZWVlZWVlIiwgImF0dGVtcHRlZF9hdCI6ICIyMDI2LTA0LTIzVDE4OjMwOjAyKzAwOjAwIn0=",
      "body_text": "{\"webhook_id\": \"12345678-1234-5678-1234-567812345678\", \"schema_version\": \"1\", \"event\": {\"event_id\": \"66666666-7777-8888-9999-aaaaaaaaaaaa\", \"event_type\": \"geofence.exit\", \"device\": {\"id\": \"dddddddd-1111-2222-3333-444444444444\", \"external_id\": \"truck-42\"}, \"geofence\": {\"id\": \"99999999-8888-7777-6666-555555555555\", \"name\": \"Origin Yard\"}, \"location\": {\"latitude\": 45.5152, \"longitude\": -122.6784, \"accuracy_meters\": 5.0}, \"triggered_at\": \"2026-04-23T18:30:00+00:00\", \"source_ping_id\": \"00000000-aaaa-bbbb-cccc-111111111111\", \"metadata\": null}, \"tenant_id\": \"aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee\", \"attempted_at\": \"2026-04-23T18:30:02+00:00\"}",
      "signing_secret": "openfence-test-vectors-secret-3",
      "timestamp_unix": 1714780000,
      "signature_header": "t=1714780000,v1=5d8ca3dc54511f2d9fa621c706081bf0d94f5f4a880aee2fa668f43e56d9b64e",
      "timestamp_header": "1714780000",
      "expected_outcome": "accepted",
      "expected_event_id": "66666666-7777-8888-9999-aaaaaaaaaaaa"
    },
    {
      "case_name": "rejected_stale_timestamp",
      "description": "Signature is mathematically valid but the timestamp is 10 minutes (600s) in the past \u2014 outside the receiver's 5-minute symmetric freshness window.  Receivers MUST reject without verifying further.",
      "body_b64": "eyJ3ZWJob29rX2lkIjogIjEyMzQ1Njc4LTEyMzQtNTY3OC0xMjM0LTU2NzgxMjM0NTY3OCIsICJzY2hlbWFfdmVyc2lvbiI6ICIxIiwgImV2ZW50IjogeyJldmVudF9pZCI6ICIxMTExMTExMS0yMjIyLTMzMzMtNDQ0NC01NTU1NTU1NTU1NTUiLCAiZXZlbnRfdHlwZSI6ICJnZW9mZW5jZS5lbnRlciIsICJkZXZpY2UiOiB7ImlkIjogImRkZGRkZGRkLTExMTEtMjIyMi0zMzMzLTQ0NDQ0NDQ0NDQ0NCIsICJleHRlcm5hbF9pZCI6ICJ0cnVjay00MiJ9LCAiZ2VvZmVuY2UiOiB7ImlkIjogIjk5OTk5OTk5LTg4ODgtNzc3Ny02NjY2LTU1NTU1NTU1NTU1NSIsICJuYW1lIjogIk9yaWdpbiBZYXJkIn0sICJsb2NhdGlvbiI6IHsibGF0aXR1ZGUiOiA0NS41MTUyLCAibG9uZ2l0dWRlIjogLTEyMi42Nzg0LCAiYWNjdXJhY3lfbWV0ZXJzIjogNS4wfSwgInRyaWdnZXJlZF9hdCI6ICIyMDI2LTA0LTIzVDE3OjQ1OjAwKzAwOjAwIiwgInNvdXJjZV9waW5nX2lkIjogIjAwMDAwMDAwLWFhYWEtYmJiYi1jY2NjLTExMTExMTExMTExMSIsICJtZXRhZGF0YSI6IG51bGx9LCAidGVuYW50X2lkIjogImFhYWFhYWFhLWJiYmItY2NjYy1kZGRkLWVlZWVlZWVlZWVlZSIsICJhdHRlbXB0ZWRfYXQiOiAiMjAyNi0wNC0yM1QxNzo0NTowNSswMDowMCJ9",
      "body_text": "{\"webhook_id\": \"12345678-1234-5678-1234-567812345678\", \"schema_version\": \"1\", \"event\": {\"event_id\": \"11111111-2222-3333-4444-555555555555\", \"event_type\": \"geofence.enter\", \"device\": {\"id\": \"dddddddd-1111-2222-3333-444444444444\", \"external_id\": \"truck-42\"}, \"geofence\": {\"id\": \"99999999-8888-7777-6666-555555555555\", \"name\": \"Origin Yard\"}, \"location\": {\"latitude\": 45.5152, \"longitude\": -122.6784, \"accuracy_meters\": 5.0}, \"triggered_at\": \"2026-04-23T17:45:00+00:00\", \"source_ping_id\": \"00000000-aaaa-bbbb-cccc-111111111111\", \"metadata\": null}, \"tenant_id\": \"aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee\", \"attempted_at\": \"2026-04-23T17:45:05+00:00\"}",
      "signing_secret": "openfence-test-vectors-secret-3",
      "timestamp_unix": 1714779400,
      "signature_header": "t=1714779400,v1=7a1ff074532602ea244f631834feb06cc90eabdb9f46e05f20f5f9218302d868",
      "timestamp_header": "1714779400",
      "expected_outcome": "rejected:stale_timestamp",
      "expected_event_id": null
    },
    {
      "case_name": "rejected_future_timestamp",
      "description": "Signature is mathematically valid but the timestamp is 10 minutes (600s) in the future \u2014 outside the symmetric freshness window.  Receivers MUST apply the window in BOTH directions to defend against clock-skew + future-dated forgery.",
      "body_b64": "eyJ3ZWJob29rX2lkIjogIjEyMzQ1Njc4LTEyMzQtNTY3OC0xMjM0LTU2NzgxMjM0NTY3OCIsICJzY2hlbWFfdmVyc2lvbiI6ICIxIiwgImV2ZW50IjogeyJldmVudF9pZCI6ICIxMTExMTExMS0yMjIyLTMzMzMtNDQ0NC01NTU1NTU1NTU1NTUiLCAiZXZlbnRfdHlwZSI6ICJnZW9mZW5jZS5lbnRlciIsICJkZXZpY2UiOiB7ImlkIjogImRkZGRkZGRkLTExMTEtMjIyMi0zMzMzLTQ0NDQ0NDQ0NDQ0NCIsICJleHRlcm5hbF9pZCI6ICJ0cnVjay00MiJ9LCAiZ2VvZmVuY2UiOiB7ImlkIjogIjk5OTk5OTk5LTg4ODgtNzc3Ny02NjY2LTU1NTU1NTU1NTU1NSIsICJuYW1lIjogIk9yaWdpbiBZYXJkIn0sICJsb2NhdGlvbiI6IHsibGF0aXR1ZGUiOiA0NS41MTUyLCAibG9uZ2l0dWRlIjogLTEyMi42Nzg0LCAiYWNjdXJhY3lfbWV0ZXJzIjogNS4wfSwgInRyaWdnZXJlZF9hdCI6ICIyMDI2LTA0LTIzVDE3OjQ1OjAwKzAwOjAwIiwgInNvdXJjZV9waW5nX2lkIjogIjAwMDAwMDAwLWFhYWEtYmJiYi1jY2NjLTExMTExMTExMTExMSIsICJtZXRhZGF0YSI6IG51bGx9LCAidGVuYW50X2lkIjogImFhYWFhYWFhLWJiYmItY2NjYy1kZGRkLWVlZWVlZWVlZWVlZSIsICJhdHRlbXB0ZWRfYXQiOiAiMjAyNi0wNC0yM1QxNzo0NTowNSswMDowMCJ9",
      "body_text": "{\"webhook_id\": \"12345678-1234-5678-1234-567812345678\", \"schema_version\": \"1\", \"event\": {\"event_id\": \"11111111-2222-3333-4444-555555555555\", \"event_type\": \"geofence.enter\", \"device\": {\"id\": \"dddddddd-1111-2222-3333-444444444444\", \"external_id\": \"truck-42\"}, \"geofence\": {\"id\": \"99999999-8888-7777-6666-555555555555\", \"name\": \"Origin Yard\"}, \"location\": {\"latitude\": 45.5152, \"longitude\": -122.6784, \"accuracy_meters\": 5.0}, \"triggered_at\": \"2026-04-23T17:45:00+00:00\", \"source_ping_id\": \"00000000-aaaa-bbbb-cccc-111111111111\", \"metadata\": null}, \"tenant_id\": \"aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee\", \"attempted_at\": \"2026-04-23T17:45:05+00:00\"}",
      "signing_secret": "openfence-test-vectors-secret-3",
      "timestamp_unix": 1714780600,
      "signature_header": "t=1714780600,v1=54b0a406fe429b325e98d366399514cdb6191799aa543dccb0fbe8b2d306a250",
      "timestamp_header": "1714780600",
      "expected_outcome": "rejected:future_timestamp",
      "expected_event_id": null
    },
    {
      "case_name": "rejected_tampered_signature",
      "description": "Headers and body match canonical values, but the v1= digest has its last hex character flipped.  Receivers MUST reject via constant-time compare; do NOT short-circuit on any prefix match.",
      "body_b64": "eyJ3ZWJob29rX2lkIjogIjEyMzQ1Njc4LTEyMzQtNTY3OC0xMjM0LTU2NzgxMjM0NTY3OCIsICJzY2hlbWFfdmVyc2lvbiI6ICIxIiwgImV2ZW50IjogeyJldmVudF9pZCI6ICIxMTExMTExMS0yMjIyLTMzMzMtNDQ0NC01NTU1NTU1NTU1NTUiLCAiZXZlbnRfdHlwZSI6ICJnZW9mZW5jZS5lbnRlciIsICJkZXZpY2UiOiB7ImlkIjogImRkZGRkZGRkLTExMTEtMjIyMi0zMzMzLTQ0NDQ0NDQ0NDQ0NCIsICJleHRlcm5hbF9pZCI6ICJ0cnVjay00MiJ9LCAiZ2VvZmVuY2UiOiB7ImlkIjogIjk5OTk5OTk5LTg4ODgtNzc3Ny02NjY2LTU1NTU1NTU1NTU1NSIsICJuYW1lIjogIk9yaWdpbiBZYXJkIn0sICJsb2NhdGlvbiI6IHsibGF0aXR1ZGUiOiA0NS41MTUyLCAibG9uZ2l0dWRlIjogLTEyMi42Nzg0LCAiYWNjdXJhY3lfbWV0ZXJzIjogNS4wfSwgInRyaWdnZXJlZF9hdCI6ICIyMDI2LTA0LTIzVDE3OjQ1OjAwKzAwOjAwIiwgInNvdXJjZV9waW5nX2lkIjogIjAwMDAwMDAwLWFhYWEtYmJiYi1jY2NjLTExMTExMTExMTExMSIsICJtZXRhZGF0YSI6IG51bGx9LCAidGVuYW50X2lkIjogImFhYWFhYWFhLWJiYmItY2NjYy1kZGRkLWVlZWVlZWVlZWVlZSIsICJhdHRlbXB0ZWRfYXQiOiAiMjAyNi0wNC0yM1QxNzo0NTowNSswMDowMCJ9",
      "body_text": "{\"webhook_id\": \"12345678-1234-5678-1234-567812345678\", \"schema_version\": \"1\", \"event\": {\"event_id\": \"11111111-2222-3333-4444-555555555555\", \"event_type\": \"geofence.enter\", \"device\": {\"id\": \"dddddddd-1111-2222-3333-444444444444\", \"external_id\": \"truck-42\"}, \"geofence\": {\"id\": \"99999999-8888-7777-6666-555555555555\", \"name\": \"Origin Yard\"}, \"location\": {\"latitude\": 45.5152, \"longitude\": -122.6784, \"accuracy_meters\": 5.0}, \"triggered_at\": \"2026-04-23T17:45:00+00:00\", \"source_ping_id\": \"00000000-aaaa-bbbb-cccc-111111111111\", \"metadata\": null}, \"tenant_id\": \"aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee\", \"attempted_at\": \"2026-04-23T17:45:05+00:00\"}",
      "signing_secret": "openfence-test-vectors-secret-3",
      "timestamp_unix": 1714780000,
      "signature_header": "t=1714780000,v1=a68792632078df04344eddc605dda3d79b04e54f4be371b9806fd92a75398090",
      "timestamp_header": "1714780000",
      "expected_outcome": "rejected:bad_signature",
      "expected_event_id": null
    },
    {
      "case_name": "rejected_tampered_body",
      "description": "Headers carry the canonical signature for body_a, but the delivered body has its last two bytes corrupted.  Receivers MUST recompute HMAC over the actually-received bytes and reject \u2014 defends against MITM body modification.",
      "body_b64": "eyJ3ZWJob29rX2lkIjogIjEyMzQ1Njc4LTEyMzQtNTY3OC0xMjM0LTU2NzgxMjM0NTY3OCIsICJzY2hlbWFfdmVyc2lvbiI6ICIxIiwgImV2ZW50IjogeyJldmVudF9pZCI6ICIxMTExMTExMS0yMjIyLTMzMzMtNDQ0NC01NTU1NTU1NTU1NTUiLCAiZXZlbnRfdHlwZSI6ICJnZW9mZW5jZS5lbnRlciIsICJkZXZpY2UiOiB7ImlkIjogImRkZGRkZGRkLTExMTEtMjIyMi0zMzMzLTQ0NDQ0NDQ0NDQ0NCIsICJleHRlcm5hbF9pZCI6ICJ0cnVjay00MiJ9LCAiZ2VvZmVuY2UiOiB7ImlkIjogIjk5OTk5OTk5LTg4ODgtNzc3Ny02NjY2LTU1NTU1NTU1NTU1NSIsICJuYW1lIjogIk9yaWdpbiBZYXJkIn0sICJsb2NhdGlvbiI6IHsibGF0aXR1ZGUiOiA0NS41MTUyLCAibG9uZ2l0dWRlIjogLTEyMi42Nzg0LCAiYWNjdXJhY3lfbWV0ZXJzIjogNS4wfSwgInRyaWdnZXJlZF9hdCI6ICIyMDI2LTA0LTIzVDE3OjQ1OjAwKzAwOjAwIiwgInNvdXJjZV9waW5nX2lkIjogIjAwMDAwMDAwLWFhYWEtYmJiYi1jY2NjLTExMTExMTExMTExMSIsICJtZXRhZGF0YSI6IG51bGx9LCAidGVuYW50X2lkIjogImFhYWFhYWFhLWJiYmItY2NjYy1kZGRkLWVlZWVlZWVlZWVlZSIsICJhdHRlbXB0ZWRfYXQiOiAiMjAyNi0wNC0yM1QxNzo0NTowNSswMDowMCF9",
      "body_text": "{\"webhook_id\": \"12345678-1234-5678-1234-567812345678\", \"schema_version\": \"1\", \"event\": {\"event_id\": \"11111111-2222-3333-4444-555555555555\", \"event_type\": \"geofence.enter\", \"device\": {\"id\": \"dddddddd-1111-2222-3333-444444444444\", \"external_id\": \"truck-42\"}, \"geofence\": {\"id\": \"99999999-8888-7777-6666-555555555555\", \"name\": \"Origin Yard\"}, \"location\": {\"latitude\": 45.5152, \"longitude\": -122.6784, \"accuracy_meters\": 5.0}, \"triggered_at\": \"2026-04-23T17:45:00+00:00\", \"source_ping_id\": \"00000000-aaaa-bbbb-cccc-111111111111\", \"metadata\": null}, \"tenant_id\": \"aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee\", \"attempted_at\": \"2026-04-23T17:45:05+00:00!}",
      "signing_secret": "openfence-test-vectors-secret-3",
      "timestamp_unix": 1714780000,
      "signature_header": "t=1714780000,v1=a68792632078df04344eddc605dda3d79b04e54f4be371b9806fd92a75398095",
      "timestamp_header": "1714780000",
      "expected_outcome": "rejected:bad_signature",
      "expected_event_id": null
    },
    {
      "case_name": "rejected_duplicate_v1_segment",
      "description": "Signature header carries the canonical v1= AND a second v1= with a different (decoy) digest.  Receivers MUST reject \u2014 duplicate keys indicate header tampering or proxy misconfig.  Defense via explicit duplicate-key detection, NOT silent dict-overwrite.",
      "body_b64": "eyJ3ZWJob29rX2lkIjogIjEyMzQ1Njc4LTEyMzQtNTY3OC0xMjM0LTU2NzgxMjM0NTY3OCIsICJzY2hlbWFfdmVyc2lvbiI6ICIxIiwgImV2ZW50IjogeyJldmVudF9pZCI6ICIxMTExMTExMS0yMjIyLTMzMzMtNDQ0NC01NTU1NTU1NTU1NTUiLCAiZXZlbnRfdHlwZSI6ICJnZW9mZW5jZS5lbnRlciIsICJkZXZpY2UiOiB7ImlkIjogImRkZGRkZGRkLTExMTEtMjIyMi0zMzMzLTQ0NDQ0NDQ0NDQ0NCIsICJleHRlcm5hbF9pZCI6ICJ0cnVjay00MiJ9LCAiZ2VvZmVuY2UiOiB7ImlkIjogIjk5OTk5OTk5LTg4ODgtNzc3Ny02NjY2LTU1NTU1NTU1NTU1NSIsICJuYW1lIjogIk9yaWdpbiBZYXJkIn0sICJsb2NhdGlvbiI6IHsibGF0aXR1ZGUiOiA0NS41MTUyLCAibG9uZ2l0dWRlIjogLTEyMi42Nzg0LCAiYWNjdXJhY3lfbWV0ZXJzIjogNS4wfSwgInRyaWdnZXJlZF9hdCI6ICIyMDI2LTA0LTIzVDE3OjQ1OjAwKzAwOjAwIiwgInNvdXJjZV9waW5nX2lkIjogIjAwMDAwMDAwLWFhYWEtYmJiYi1jY2NjLTExMTExMTExMTExMSIsICJtZXRhZGF0YSI6IG51bGx9LCAidGVuYW50X2lkIjogImFhYWFhYWFhLWJiYmItY2NjYy1kZGRkLWVlZWVlZWVlZWVlZSIsICJhdHRlbXB0ZWRfYXQiOiAiMjAyNi0wNC0yM1QxNzo0NTowNSswMDowMCJ9",
      "body_text": "{\"webhook_id\": \"12345678-1234-5678-1234-567812345678\", \"schema_version\": \"1\", \"event\": {\"event_id\": \"11111111-2222-3333-4444-555555555555\", \"event_type\": \"geofence.enter\", \"device\": {\"id\": \"dddddddd-1111-2222-3333-444444444444\", \"external_id\": \"truck-42\"}, \"geofence\": {\"id\": \"99999999-8888-7777-6666-555555555555\", \"name\": \"Origin Yard\"}, \"location\": {\"latitude\": 45.5152, \"longitude\": -122.6784, \"accuracy_meters\": 5.0}, \"triggered_at\": \"2026-04-23T17:45:00+00:00\", \"source_ping_id\": \"00000000-aaaa-bbbb-cccc-111111111111\", \"metadata\": null}, \"tenant_id\": \"aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee\", \"attempted_at\": \"2026-04-23T17:45:05+00:00\"}",
      "signing_secret": "openfence-test-vectors-secret-3",
      "timestamp_unix": 1714780000,
      "signature_header": "t=1714780000,v1=a68792632078df04344eddc605dda3d79b04e54f4be371b9806fd92a75398095,v1=bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb",
      "timestamp_header": "1714780000",
      "expected_outcome": "rejected:duplicate_key",
      "expected_event_id": null
    },
    {
      "case_name": "rejected_sibling_timestamp_mismatch",
      "description": "X-OpenFence-Signature carries t=<canonical_ts>; sibling X-OpenFence-Timestamp carries canonical_ts+1.  The t= segment is authoritative for HMAC; the sibling is convenience.  Receivers MUST cross-check the two and reject any mismatch as a tampering signal.",
      "body_b64": "eyJ3ZWJob29rX2lkIjogIjEyMzQ1Njc4LTEyMzQtNTY3OC0xMjM0LTU2NzgxMjM0NTY3OCIsICJzY2hlbWFfdmVyc2lvbiI6ICIxIiwgImV2ZW50IjogeyJldmVudF9pZCI6ICIxMTExMTExMS0yMjIyLTMzMzMtNDQ0NC01NTU1NTU1NTU1NTUiLCAiZXZlbnRfdHlwZSI6ICJnZW9mZW5jZS5lbnRlciIsICJkZXZpY2UiOiB7ImlkIjogImRkZGRkZGRkLTExMTEtMjIyMi0zMzMzLTQ0NDQ0NDQ0NDQ0NCIsICJleHRlcm5hbF9pZCI6ICJ0cnVjay00MiJ9LCAiZ2VvZmVuY2UiOiB7ImlkIjogIjk5OTk5OTk5LTg4ODgtNzc3Ny02NjY2LTU1NTU1NTU1NTU1NSIsICJuYW1lIjogIk9yaWdpbiBZYXJkIn0sICJsb2NhdGlvbiI6IHsibGF0aXR1ZGUiOiA0NS41MTUyLCAibG9uZ2l0dWRlIjogLTEyMi42Nzg0LCAiYWNjdXJhY3lfbWV0ZXJzIjogNS4wfSwgInRyaWdnZXJlZF9hdCI6ICIyMDI2LTA0LTIzVDE3OjQ1OjAwKzAwOjAwIiwgInNvdXJjZV9waW5nX2lkIjogIjAwMDAwMDAwLWFhYWEtYmJiYi1jY2NjLTExMTExMTExMTExMSIsICJtZXRhZGF0YSI6IG51bGx9LCAidGVuYW50X2lkIjogImFhYWFhYWFhLWJiYmItY2NjYy1kZGRkLWVlZWVlZWVlZWVlZSIsICJhdHRlbXB0ZWRfYXQiOiAiMjAyNi0wNC0yM1QxNzo0NTowNSswMDowMCJ9",
      "body_text": "{\"webhook_id\": \"12345678-1234-5678-1234-567812345678\", \"schema_version\": \"1\", \"event\": {\"event_id\": \"11111111-2222-3333-4444-555555555555\", \"event_type\": \"geofence.enter\", \"device\": {\"id\": \"dddddddd-1111-2222-3333-444444444444\", \"external_id\": \"truck-42\"}, \"geofence\": {\"id\": \"99999999-8888-7777-6666-555555555555\", \"name\": \"Origin Yard\"}, \"location\": {\"latitude\": 45.5152, \"longitude\": -122.6784, \"accuracy_meters\": 5.0}, \"triggered_at\": \"2026-04-23T17:45:00+00:00\", \"source_ping_id\": \"00000000-aaaa-bbbb-cccc-111111111111\", \"metadata\": null}, \"tenant_id\": \"aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee\", \"attempted_at\": \"2026-04-23T17:45:05+00:00\"}",
      "signing_secret": "openfence-test-vectors-secret-3",
      "timestamp_unix": 1714780000,
      "signature_header": "t=1714780000,v1=a68792632078df04344eddc605dda3d79b04e54f4be371b9806fd92a75398095",
      "timestamp_header": "1714780001",
      "expected_outcome": "rejected:sibling_mismatch",
      "expected_event_id": null
    },
    {
      "case_name": "rejected_malformed_t_segment",
      "description": "X-OpenFence-Signature carries 't=not-a-number,v1=...'.  Receivers MUST reject; an int parse failure on t= is not a recoverable state \u2014 return a 4xx, do not raise to the caller, do not attempt verification.",
      "body_b64": "eyJ3ZWJob29rX2lkIjogIjEyMzQ1Njc4LTEyMzQtNTY3OC0xMjM0LTU2NzgxMjM0NTY3OCIsICJzY2hlbWFfdmVyc2lvbiI6ICIxIiwgImV2ZW50IjogeyJldmVudF9pZCI6ICIxMTExMTExMS0yMjIyLTMzMzMtNDQ0NC01NTU1NTU1NTU1NTUiLCAiZXZlbnRfdHlwZSI6ICJnZW9mZW5jZS5lbnRlciIsICJkZXZpY2UiOiB7ImlkIjogImRkZGRkZGRkLTExMTEtMjIyMi0zMzMzLTQ0NDQ0NDQ0NDQ0NCIsICJleHRlcm5hbF9pZCI6ICJ0cnVjay00MiJ9LCAiZ2VvZmVuY2UiOiB7ImlkIjogIjk5OTk5OTk5LTg4ODgtNzc3Ny02NjY2LTU1NTU1NTU1NTU1NSIsICJuYW1lIjogIk9yaWdpbiBZYXJkIn0sICJsb2NhdGlvbiI6IHsibGF0aXR1ZGUiOiA0NS41MTUyLCAibG9uZ2l0dWRlIjogLTEyMi42Nzg0LCAiYWNjdXJhY3lfbWV0ZXJzIjogNS4wfSwgInRyaWdnZXJlZF9hdCI6ICIyMDI2LTA0LTIzVDE3OjQ1OjAwKzAwOjAwIiwgInNvdXJjZV9waW5nX2lkIjogIjAwMDAwMDAwLWFhYWEtYmJiYi1jY2NjLTExMTExMTExMTExMSIsICJtZXRhZGF0YSI6IG51bGx9LCAidGVuYW50X2lkIjogImFhYWFhYWFhLWJiYmItY2NjYy1kZGRkLWVlZWVlZWVlZWVlZSIsICJhdHRlbXB0ZWRfYXQiOiAiMjAyNi0wNC0yM1QxNzo0NTowNSswMDowMCJ9",
      "body_text": "{\"webhook_id\": \"12345678-1234-5678-1234-567812345678\", \"schema_version\": \"1\", \"event\": {\"event_id\": \"11111111-2222-3333-4444-555555555555\", \"event_type\": \"geofence.enter\", \"device\": {\"id\": \"dddddddd-1111-2222-3333-444444444444\", \"external_id\": \"truck-42\"}, \"geofence\": {\"id\": \"99999999-8888-7777-6666-555555555555\", \"name\": \"Origin Yard\"}, \"location\": {\"latitude\": 45.5152, \"longitude\": -122.6784, \"accuracy_meters\": 5.0}, \"triggered_at\": \"2026-04-23T17:45:00+00:00\", \"source_ping_id\": \"00000000-aaaa-bbbb-cccc-111111111111\", \"metadata\": null}, \"tenant_id\": \"aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee\", \"attempted_at\": \"2026-04-23T17:45:05+00:00\"}",
      "signing_secret": "openfence-test-vectors-secret-3",
      "timestamp_unix": 1714780000,
      "signature_header": "t=not-a-number,v1=aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
      "timestamp_header": "1714780000",
      "expected_outcome": "rejected:malformed_t",
      "expected_event_id": null
    },
    {
      "case_name": "rejected_malformed_v1_segment",
      "description": "X-OpenFence-Signature carries a v1= value that is not 64 lowercase-hex chars (SHA-256 digest length).  Receivers MUST validate the shape via regex BEFORE constant-time compare \u2014 defense-in-depth around any length-leak in the comparator and against malformed-input crashes.",
      "body_b64": "eyJ3ZWJob29rX2lkIjogIjEyMzQ1Njc4LTEyMzQtNTY3OC0xMjM0LTU2NzgxMjM0NTY3OCIsICJzY2hlbWFfdmVyc2lvbiI6ICIxIiwgImV2ZW50IjogeyJldmVudF9pZCI6ICIxMTExMTExMS0yMjIyLTMzMzMtNDQ0NC01NTU1NTU1NTU1NTUiLCAiZXZlbnRfdHlwZSI6ICJnZW9mZW5jZS5lbnRlciIsICJkZXZpY2UiOiB7ImlkIjogImRkZGRkZGRkLTExMTEtMjIyMi0zMzMzLTQ0NDQ0NDQ0NDQ0NCIsICJleHRlcm5hbF9pZCI6ICJ0cnVjay00MiJ9LCAiZ2VvZmVuY2UiOiB7ImlkIjogIjk5OTk5OTk5LTg4ODgtNzc3Ny02NjY2LTU1NTU1NTU1NTU1NSIsICJuYW1lIjogIk9yaWdpbiBZYXJkIn0sICJsb2NhdGlvbiI6IHsibGF0aXR1ZGUiOiA0NS41MTUyLCAibG9uZ2l0dWRlIjogLTEyMi42Nzg0LCAiYWNjdXJhY3lfbWV0ZXJzIjogNS4wfSwgInRyaWdnZXJlZF9hdCI6ICIyMDI2LTA0LTIzVDE3OjQ1OjAwKzAwOjAwIiwgInNvdXJjZV9waW5nX2lkIjogIjAwMDAwMDAwLWFhYWEtYmJiYi1jY2NjLTExMTExMTExMTExMSIsICJtZXRhZGF0YSI6IG51bGx9LCAidGVuYW50X2lkIjogImFhYWFhYWFhLWJiYmItY2NjYy1kZGRkLWVlZWVlZWVlZWVlZSIsICJhdHRlbXB0ZWRfYXQiOiAiMjAyNi0wNC0yM1QxNzo0NTowNSswMDowMCJ9",
      "body_text": "{\"webhook_id\": \"12345678-1234-5678-1234-567812345678\", \"schema_version\": \"1\", \"event\": {\"event_id\": \"11111111-2222-3333-4444-555555555555\", \"event_type\": \"geofence.enter\", \"device\": {\"id\": \"dddddddd-1111-2222-3333-444444444444\", \"external_id\": \"truck-42\"}, \"geofence\": {\"id\": \"99999999-8888-7777-6666-555555555555\", \"name\": \"Origin Yard\"}, \"location\": {\"latitude\": 45.5152, \"longitude\": -122.6784, \"accuracy_meters\": 5.0}, \"triggered_at\": \"2026-04-23T17:45:00+00:00\", \"source_ping_id\": \"00000000-aaaa-bbbb-cccc-111111111111\", \"metadata\": null}, \"tenant_id\": \"aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee\", \"attempted_at\": \"2026-04-23T17:45:05+00:00\"}",
      "signing_secret": "openfence-test-vectors-secret-3",
      "timestamp_unix": 1714780000,
      "signature_header": "t=1714780000,v1=tooshort",
      "timestamp_header": "1714780000",
      "expected_outcome": "rejected:malformed_v1",
      "expected_event_id": null
    },
    {
      "case_name": "valid_non_utf8_body",
      "description": "Body is arbitrary non-UTF-8 bytes.  OpenFence's runtime sender does not currently emit such bodies (JSON output is always ASCII), but compute_webhook_signature handles arbitrary bytes via byte-level concatenation: f'{t}.'.encode('ascii') + body.  Receivers MUST verify against the RAW received bytes \u2014 decode-then-re-encode would raise UnicodeDecodeError here.  Accept on signature match; expected_event_id is null because the body isn't JSON.",
      "body_b64": "//4AAQID/w==",
      "body_text": null,
      "signing_secret": "openfence-test-vectors-secret-3",
      "timestamp_unix": 1714780000,
      "signature_header": "t=1714780000,v1=0229a77a8cc2732758a2611d04cc4e5cb3ad27b709c0fe9ed3296708702c6e3f",
      "timestamp_header": "1714780000",
      "expected_outcome": "accepted",
      "expected_event_id": null
    }
  ]
}
