{
  "$comment": "NORMATIVE test vectors for the bb-sign webhook signature scheme. Generated by #99; consumed by the Java and JavaScript SDKs, which are forbidden to define their own. See docs/api/webhook-signature-spec.md section 7 — the case names here are exactly its rows.",
  "algorithm": "HMAC-SHA256 over '<t>.' + raw_body, lowercase hex",
  "toleranceSeconds": 300,
  "fieldNotes": {
    "secret": "base64; the HMAC key is these base64 CHARACTERS as UTF-8 bytes, matching the server",
    "bodyUtf8": "the request body as a UTF-8 string; encode to bytes before hashing",
    "header": "the literal X-BBSign-Signature value, not structured fields",
    "now": "the clock passed to the verifier",
    "signerReproducible": "true when a SIGNER can emit exactly this header; the rest are receiver-side only"
  },
  "cases": [
    {
      "name": "valid signature, fresh timestamp",
      "secret": "YmItc2lnbi10ZXN0LXNlY3JldC0zMi1ieXRlcyEhISE=",
      "bodyUtf8": "{\"id\":\"evt_1\",\"type\":\"envelope.completed\",\"apiVersion\":\"2026-08-09\",\"createdAt\":\"2026-08-09T14:22:31Z\",\"data\":{}}",
      "header": "t=1786000000,v1=04269e69f9dd67eba299d22efa0ab37abf2b69deb1d622cbe8be27121b0fe60b",
      "now": 1786000005,
      "expected": true,
      "signerReproducible": true,
      "note": "The base case. Given this t, secret and body, the signer emits exactly this header."
    },
    {
      "name": "valid, at exactly +300s",
      "secret": "YmItc2lnbi10ZXN0LXNlY3JldC0zMi1ieXRlcyEhISE=",
      "bodyUtf8": "{\"id\":\"evt_1\",\"type\":\"envelope.completed\",\"apiVersion\":\"2026-08-09\",\"createdAt\":\"2026-08-09T14:22:31Z\",\"data\":{}}",
      "header": "t=1786000000,v1=04269e69f9dd67eba299d22efa0ab37abf2b69deb1d622cbe8be27121b0fe60b",
      "now": 1786000300,
      "expected": true,
      "signerReproducible": true,
      "note": "The boundary is inclusive. Same header as the base case, evaluated at a different clock."
    },
    {
      "name": "valid, at +301s",
      "secret": "YmItc2lnbi10ZXN0LXNlY3JldC0zMi1ieXRlcyEhISE=",
      "bodyUtf8": "{\"id\":\"evt_1\",\"type\":\"envelope.completed\",\"apiVersion\":\"2026-08-09\",\"createdAt\":\"2026-08-09T14:22:31Z\",\"data\":{}}",
      "header": "t=1786000000,v1=04269e69f9dd67eba299d22efa0ab37abf2b69deb1d622cbe8be27121b0fe60b",
      "now": 1786000301,
      "expected": false,
      "signerReproducible": false,
      "note": "One second past the window. Replay protection."
    },
    {
      "name": "valid, at -301s",
      "secret": "YmItc2lnbi10ZXN0LXNlY3JldC0zMi1ieXRlcyEhISE=",
      "bodyUtf8": "{\"id\":\"evt_1\",\"type\":\"envelope.completed\",\"apiVersion\":\"2026-08-09\",\"createdAt\":\"2026-08-09T14:22:31Z\",\"data\":{}}",
      "header": "t=1786000000,v1=04269e69f9dd67eba299d22efa0ab37abf2b69deb1d622cbe8be27121b0fe60b",
      "now": 1785999699,
      "expected": false,
      "signerReproducible": false,
      "note": "Symmetric, to tolerate clock skew in both directions rather than only one."
    },
    {
      "name": "tampered body, one byte changed",
      "secret": "YmItc2lnbi10ZXN0LXNlY3JldC0zMi1ieXRlcyEhISE=",
      "bodyUtf8": "{\"id\":\"evt_2\",\"type\":\"envelope.completed\",\"apiVersion\":\"2026-08-09\",\"createdAt\":\"2026-08-09T14:22:31Z\",\"data\":{}}",
      "header": "t=1786000000,v1=04269e69f9dd67eba299d22efa0ab37abf2b69deb1d622cbe8be27121b0fe60b",
      "now": 1786000005,
      "expected": false,
      "signerReproducible": false,
      "note": "The signature is over the body, so one changed byte invalidates it."
    },
    {
      "name": "wrong secret",
      "secret": "YS1jb21wbGV0ZWx5LWRpZmZlcmVudC1zZWNyZXQhISE=",
      "bodyUtf8": "{\"id\":\"evt_1\",\"type\":\"envelope.completed\",\"apiVersion\":\"2026-08-09\",\"createdAt\":\"2026-08-09T14:22:31Z\",\"data\":{}}",
      "header": "t=1786000000,v1=04269e69f9dd67eba299d22efa0ab37abf2b69deb1d622cbe8be27121b0fe60b",
      "now": 1786000005,
      "expected": false,
      "signerReproducible": false,
      "note": "Verifying with a secret that did not sign it."
    },
    {
      "name": "two v1 values, second one correct (rotation)",
      "secret": "YmItc2lnbi10ZXN0LXNlY3JldC0zMi1ieXRlcyEhISE=",
      "bodyUtf8": "{\"id\":\"evt_1\",\"type\":\"envelope.completed\",\"apiVersion\":\"2026-08-09\",\"createdAt\":\"2026-08-09T14:22:31Z\",\"data\":{}}",
      "header": "t=1786000000,v1=7aba4afded797094872cd511c1cd9da544176862883a31a2b0f56dc1192de777,v1=04269e69f9dd67eba299d22efa0ab37abf2b69deb1d622cbe8be27121b0fe60b",
      "now": 1786000005,
      "expected": true,
      "signerReproducible": true,
      "note": "THE rotation case. A receiver holding the old secret matches the second value. A verifier that reads only the first v1 breaks every rotation, which is why the spec says 'any'."
    },
    {
      "name": "unknown scheme v2= present alongside valid v1",
      "secret": "YmItc2lnbi10ZXN0LXNlY3JldC0zMi1ieXRlcyEhISE=",
      "bodyUtf8": "{\"id\":\"evt_1\",\"type\":\"envelope.completed\",\"apiVersion\":\"2026-08-09\",\"createdAt\":\"2026-08-09T14:22:31Z\",\"data\":{}}",
      "header": "t=1786000000,v1=04269e69f9dd67eba299d22efa0ab37abf2b69deb1d622cbe8be27121b0fe60b,v2=somethingnobodyunderstandsyet",
      "now": 1786000005,
      "expected": true,
      "signerReproducible": false,
      "note": "Unknown schemes are IGNORED, not rejected — that is what makes a future v2 non-breaking. Not signer-reproducible: bb-sign never emits v2."
    },
    {
      "name": "header with whitespace after commas",
      "secret": "YmItc2lnbi10ZXN0LXNlY3JldC0zMi1ieXRlcyEhISE=",
      "bodyUtf8": "{\"id\":\"evt_1\",\"type\":\"envelope.completed\",\"apiVersion\":\"2026-08-09\",\"createdAt\":\"2026-08-09T14:22:31Z\",\"data\":{}}",
      "header": "t=1786000000, v1=04269e69f9dd67eba299d22efa0ab37abf2b69deb1d622cbe8be27121b0fe60b",
      "now": 1786000005,
      "expected": true,
      "signerReproducible": false,
      "note": "Receivers must tolerate whitespace even though senders do not emit it — proxies and hand-written clients add it."
    },
    {
      "name": "missing header",
      "secret": "YmItc2lnbi10ZXN0LXNlY3JldC0zMi1ieXRlcyEhISE=",
      "bodyUtf8": "{\"id\":\"evt_1\",\"type\":\"envelope.completed\",\"apiVersion\":\"2026-08-09\",\"createdAt\":\"2026-08-09T14:22:31Z\",\"data\":{}}",
      "header": "",
      "now": 1786000005,
      "expected": false,
      "signerReproducible": false,
      "note": "Returns false, never throws."
    },
    {
      "name": "missing t",
      "secret": "YmItc2lnbi10ZXN0LXNlY3JldC0zMi1ieXRlcyEhISE=",
      "bodyUtf8": "{\"id\":\"evt_1\",\"type\":\"envelope.completed\",\"apiVersion\":\"2026-08-09\",\"createdAt\":\"2026-08-09T14:22:31Z\",\"data\":{}}",
      "header": "v1=04269e69f9dd67eba299d22efa0ab37abf2b69deb1d622cbe8be27121b0fe60b",
      "now": 1786000005,
      "expected": false,
      "signerReproducible": false,
      "note": "Without t there is no freshness to check."
    },
    {
      "name": "v1 not hex",
      "secret": "YmItc2lnbi10ZXN0LXNlY3JldC0zMi1ieXRlcyEhISE=",
      "bodyUtf8": "{\"id\":\"evt_1\",\"type\":\"envelope.completed\",\"apiVersion\":\"2026-08-09\",\"createdAt\":\"2026-08-09T14:22:31Z\",\"data\":{}}",
      "header": "t=1786000000,v1=zzzznothexatallzzzz",
      "now": 1786000005,
      "expected": false,
      "signerReproducible": false,
      "note": "Must not throw on a parse failure."
    },
    {
      "name": "v1 wrong length",
      "secret": "YmItc2lnbi10ZXN0LXNlY3JldC0zMi1ieXRlcyEhISE=",
      "bodyUtf8": "{\"id\":\"evt_1\",\"type\":\"envelope.completed\",\"apiVersion\":\"2026-08-09\",\"createdAt\":\"2026-08-09T14:22:31Z\",\"data\":{}}",
      "header": "t=1786000000,v1=04269e69f9dd67eba299d22efa0ab37a",
      "now": 1786000005,
      "expected": false,
      "signerReproducible": false,
      "note": "A truncated signature must fail, and the compare must stay constant-time."
    },
    {
      "name": "empty body",
      "secret": "YmItc2lnbi10ZXN0LXNlY3JldC0zMi1ieXRlcyEhISE=",
      "bodyUtf8": "",
      "header": "t=1786000000,v1=18e8c889f255c01fd15fcff6a5575058c4fb322f31c76b64655a42aa27514ba5",
      "now": 1786000005,
      "expected": true,
      "signerReproducible": true,
      "note": "'per vector' in the spec; fixed here as a correctly-signed empty body, which is the case an implementation is most likely to special-case wrongly."
    },
    {
      "name": "body with non-ASCII UTF-8",
      "secret": "YmItc2lnbi10ZXN0LXNlY3JldC0zMi1ieXRlcyEhISE=",
      "bodyUtf8": "{\"id\":\"evt_2\",\"type\":\"envelope.sent\",\"data\":{\"title\":\"Contrato de Prestación — Señor Muñoz 🖋\"}}",
      "header": "t=1786000000,v1=621f61a21042352616f674fe450e5b8427ff261a1b6fef5472cad4aa297a0dab",
      "now": 1786000005,
      "expected": true,
      "signerReproducible": true,
      "note": "Signing is defined over BYTES. An implementation that hashes a decoded string, or re-encodes it, fails here and only here."
    },
    {
      "name": "t out of numeric range",
      "secret": "YmItc2lnbi10ZXN0LXNlY3JldC0zMi1ieXRlcyEhISE=",
      "bodyUtf8": "{\"id\":\"evt_1\",\"type\":\"envelope.completed\",\"apiVersion\":\"2026-08-09\",\"createdAt\":\"2026-08-09T14:22:31Z\",\"data\":{}}",
      "header": "t=999999999999999999999999,v1=04269e69f9dd67eba299d22efa0ab37abf2b69deb1d622cbe8be27121b0fe60b",
      "now": 1786000005,
      "expected": false,
      "signerReproducible": false,
      "note": "Must return false rather than overflow or throw."
    }
  ]
}
