{
  "description": "pixevel.chat business webhooks (#153, rotation #215): X-Pixevel-Signature = \"v1=\" + lowercase hex(HMAC-SHA256(secret, \"<X-Pixevel-Timestamp>.\" + body)), over the exact body bytes (UTF-8). During a secret rotation the header is two of those joined by a comma, old then new (`rotate`). `sign` cases are what services/chat produces for one secret (internal/webhooks/signature_test.go); `verify` cases are what a receiver must decide, with its clock at `now` and a tolerance of `toleranceSeconds` either way (packages/api/src/chat/webhooks/signature.test.ts). A receiver accepts the delivery when ANY v1= part matches its secret. Values generated with the Go Sign and checked with HMAC-SHA256.",
  "sign": [
    {
      "name": "ascii body",
      "secret": "whsec_0123456789abcdef0123456789abcdef01234567",
      "timestamp": 1759399200,
      "body": "{\"event\":\"webhook.test\",\"occurredAt\":\"2026-10-02T10:00:00.000Z\",\"workspaceId\":\"9c0f42b9-5d2e-4c1a-8f00-0000000000a1\",\"webhookId\":\"0199a3c0-0000-7000-8000-0000000000b1\"}",
      "signature": "v1=504d4a40d7711cf10c4f3a1a7100845f0c0701744638108b3c32a0a06fa7a278"
    },
    {
      "name": "Persian UTF-8 body",
      "secret": "whsec_0123456789abcdef0123456789abcdef01234567",
      "timestamp": 1759399201,
      "body": "{\"event\":\"conversation.customer_replied\",\"message\":{\"body\":\"سلام، سفارش ۱۲۳۴ من کی می‌رسد؟\"}}",
      "signature": "v1=734f2a61733f79d51e5325d68f7f2ccb270c08f35a5d3751358de63106d5d6dd"
    },
    {
      "name": "newlines and escaped unicode",
      "secret": "whsec_ffffffffffffffffffffffffffffffffffffffff",
      "timestamp": 1700000000,
      "body": "{\n  \"text\": \"caf\\u00e9 \\\"quoted\\\"\",\n  \"tab\": \"a\\tb\"\n}\n",
      "signature": "v1=11cfbc14bde0b5995cc9cc34f265050e39526309178aa6decc46a0b7b0d15094"
    }
  ],
  "rotate": [
    {
      "name": "two signatures during the grace window",
      "secrets": [
        "whsec_0123456789abcdef0123456789abcdef01234567",
        "whsec_aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
      ],
      "timestamp": 1759399200,
      "body": "{\"ok\":true}",
      "signature": "v1=c9f5d1c0e7b4f1499e98c243aee7665d1c68667ab60d5027d30ec40a432aa6ab,v1=a0b68d14fdef41130c8ef8b73c83cf1f9f562194c631234ea4d68ea91035fce6"
    }
  ],
  "verify": [
    {
      "name": "valid",
      "secret": "whsec_0123456789abcdef0123456789abcdef01234567",
      "timestamp": 1759399200,
      "body": "{\"event\":\"webhook.test\",\"occurredAt\":\"2026-10-02T10:00:00.000Z\",\"workspaceId\":\"9c0f42b9-5d2e-4c1a-8f00-0000000000a1\",\"webhookId\":\"0199a3c0-0000-7000-8000-0000000000b1\"}",
      "header": "v1=504d4a40d7711cf10c4f3a1a7100845f0c0701744638108b3c32a0a06fa7a278",
      "now": 1759399260,
      "toleranceSeconds": 300,
      "valid": true
    },
    {
      "name": "valid at the edge of the tolerance",
      "secret": "whsec_0123456789abcdef0123456789abcdef01234567",
      "timestamp": 1759399200,
      "body": "{\"event\":\"webhook.test\",\"occurredAt\":\"2026-10-02T10:00:00.000Z\",\"workspaceId\":\"9c0f42b9-5d2e-4c1a-8f00-0000000000a1\",\"webhookId\":\"0199a3c0-0000-7000-8000-0000000000b1\"}",
      "header": "v1=504d4a40d7711cf10c4f3a1a7100845f0c0701744638108b3c32a0a06fa7a278",
      "now": 1759399500,
      "toleranceSeconds": 300,
      "valid": true
    },
    {
      "name": "tampered body",
      "secret": "whsec_0123456789abcdef0123456789abcdef01234567",
      "timestamp": 1759399200,
      "body": "{\"event\":\"webhook.tset\",\"occurredAt\":\"2026-10-02T10:00:00.000Z\",\"workspaceId\":\"9c0f42b9-5d2e-4c1a-8f00-0000000000a1\",\"webhookId\":\"0199a3c0-0000-7000-8000-0000000000b1\"}",
      "header": "v1=504d4a40d7711cf10c4f3a1a7100845f0c0701744638108b3c32a0a06fa7a278",
      "now": 1759399260,
      "toleranceSeconds": 300,
      "valid": false
    },
    {
      "name": "wrong secret",
      "secret": "whsec_ffffffffffffffffffffffffffffffffffffffff",
      "timestamp": 1759399200,
      "body": "{\"event\":\"webhook.test\",\"occurredAt\":\"2026-10-02T10:00:00.000Z\",\"workspaceId\":\"9c0f42b9-5d2e-4c1a-8f00-0000000000a1\",\"webhookId\":\"0199a3c0-0000-7000-8000-0000000000b1\"}",
      "header": "v1=504d4a40d7711cf10c4f3a1a7100845f0c0701744638108b3c32a0a06fa7a278",
      "now": 1759399260,
      "toleranceSeconds": 300,
      "valid": false
    },
    {
      "name": "stale timestamp",
      "secret": "whsec_0123456789abcdef0123456789abcdef01234567",
      "timestamp": 1759399200,
      "body": "{\"event\":\"webhook.test\",\"occurredAt\":\"2026-10-02T10:00:00.000Z\",\"workspaceId\":\"9c0f42b9-5d2e-4c1a-8f00-0000000000a1\",\"webhookId\":\"0199a3c0-0000-7000-8000-0000000000b1\"}",
      "header": "v1=504d4a40d7711cf10c4f3a1a7100845f0c0701744638108b3c32a0a06fa7a278",
      "now": 1759399501,
      "toleranceSeconds": 300,
      "valid": false
    },
    {
      "name": "timestamp too far in the future",
      "secret": "whsec_0123456789abcdef0123456789abcdef01234567",
      "timestamp": 1759399200,
      "body": "{\"event\":\"webhook.test\",\"occurredAt\":\"2026-10-02T10:00:00.000Z\",\"workspaceId\":\"9c0f42b9-5d2e-4c1a-8f00-0000000000a1\",\"webhookId\":\"0199a3c0-0000-7000-8000-0000000000b1\"}",
      "header": "v1=504d4a40d7711cf10c4f3a1a7100845f0c0701744638108b3c32a0a06fa7a278",
      "now": 1759398899,
      "toleranceSeconds": 300,
      "valid": false
    },
    {
      "name": "missing v1= prefix",
      "secret": "whsec_0123456789abcdef0123456789abcdef01234567",
      "timestamp": 1759399200,
      "body": "{\"event\":\"webhook.test\",\"occurredAt\":\"2026-10-02T10:00:00.000Z\",\"workspaceId\":\"9c0f42b9-5d2e-4c1a-8f00-0000000000a1\",\"webhookId\":\"0199a3c0-0000-7000-8000-0000000000b1\"}",
      "header": "504d4a40d7711cf10c4f3a1a7100845f0c0701744638108b3c32a0a06fa7a278",
      "now": 1759399260,
      "toleranceSeconds": 300,
      "valid": false
    },
    {
      "name": "uppercase hex is accepted",
      "secret": "whsec_0123456789abcdef0123456789abcdef01234567",
      "timestamp": 1759399200,
      "body": "{\"event\":\"webhook.test\",\"occurredAt\":\"2026-10-02T10:00:00.000Z\",\"workspaceId\":\"9c0f42b9-5d2e-4c1a-8f00-0000000000a1\",\"webhookId\":\"0199a3c0-0000-7000-8000-0000000000b1\"}",
      "header": "v1=504D4A40D7711CF10C4F3A1A7100845F0C0701744638108B3C32A0A06FA7A278",
      "now": 1759399260,
      "toleranceSeconds": 300,
      "valid": true
    },
    {
      "name": "old secret during a rotation",
      "secret": "whsec_0123456789abcdef0123456789abcdef01234567",
      "timestamp": 1759399200,
      "body": "{\"ok\":true}",
      "header": "v1=c9f5d1c0e7b4f1499e98c243aee7665d1c68667ab60d5027d30ec40a432aa6ab,v1=a0b68d14fdef41130c8ef8b73c83cf1f9f562194c631234ea4d68ea91035fce6",
      "now": 1759399260,
      "toleranceSeconds": 300,
      "valid": true
    },
    {
      "name": "new secret during a rotation",
      "secret": "whsec_aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
      "timestamp": 1759399200,
      "body": "{\"ok\":true}",
      "header": "v1=c9f5d1c0e7b4f1499e98c243aee7665d1c68667ab60d5027d30ec40a432aa6ab,v1=a0b68d14fdef41130c8ef8b73c83cf1f9f562194c631234ea4d68ea91035fce6",
      "now": 1759399260,
      "toleranceSeconds": 300,
      "valid": true
    },
    {
      "name": "neither secret of a rotation",
      "secret": "whsec_ffffffffffffffffffffffffffffffffffffffff",
      "timestamp": 1759399200,
      "body": "{\"ok\":true}",
      "header": "v1=c9f5d1c0e7b4f1499e98c243aee7665d1c68667ab60d5027d30ec40a432aa6ab,v1=a0b68d14fdef41130c8ef8b73c83cf1f9f562194c631234ea4d68ea91035fce6",
      "now": 1759399260,
      "toleranceSeconds": 300,
      "valid": false
    }
  ]
}
