Skip to content

feat(webhooks): verifyAndParse* API for compressed payloads (CHA-3071)#294

Merged
nijeesh-stream merged 3 commits into
mainfrom
nijeeshjoshy/cha-3071-compress-webhook-payloads
May 12, 2026
Merged

feat(webhooks): verifyAndParse* API for compressed payloads (CHA-3071)#294
nijeesh-stream merged 3 commits into
mainfrom
nijeeshjoshy/cha-3071-compress-webhook-payloads

Conversation

@nijeesh-stream
Copy link
Copy Markdown
Contributor

Summary

Adds first-class support for gzip-compressed webhook payloads (HTTP webhooks, SQS, SNS) and exposes the stable verifyAndParse* / parseSqs / parseSns API that the other Stream SDKs ship as part of the CHA-3071 rollout. Linear ticket: CHA-3071.

New public API (src/utils/webhook.ts)

Primitives:

  • gunzipPayload(body) — gzip-magic-byte detection (1f 8b, per RFC 1952), no-op when not compressed
  • decodeSqsPayload(body) — strict base64 decode then gunzip-if-magic
  • decodeSnsPayload(notificationBody) — JSON-parse the SNS HTTP notification envelope, extract the inner Message, then run the SQS pipeline. Falls through to a pre-extracted Message string when the input is not a JSON envelope
  • verifySignature(body, signature, secret) — HMAC-SHA256 over the uncompressed body, with a constant-time comparison
  • parseEvent(payload) — JSON → typed WHEvent

Composites (return typed WHEvent):

  • verifyAndParseWebhook(rawBody, signature, secret)
  • parseSqs(messageBody)
  • parseSns(notificationBody)

StreamClient#verifyAndParseWebhook / parseSqs / parseSns use the configured client secret automatically.

Backwards compatibility

StreamClient#verifyWebhook (boolean) is unchanged.

Unified error handling

Every webhook failure path now terminates at a single error class — InvalidWebhookError — so customers only need one catch arm. The message identifies which failure mode fired; the InvalidWebhookErrorMessages constants are exported for callers that prefer exact-match filtering:

Failure mode Message
Signature mismatch signature mismatch
Base64 decode invalid base64 encoding
Gzip decompression gzip decompression failed
JSON parse invalid JSON payload

Cross-references

Companion SDK PRs that ship the same contract:

Test plan

  • npx vitest run __tests__/webhook-compression.test.ts — 35 passed
  • yarn lint — clean
  • yarn build — clean

Adds first-class support for gzip-compressed webhook payloads (HTTP
webhooks, SQS, SNS) and exposes the cross-SDK verifyAndParse* / parseSqs
/ parseSns contract that the other Stream SDKs ship in the CHA-3071
rollout.

New module: src/utils/webhook.ts

Primitives:
- gunzipPayload(body)          - gzip-magic-byte detection (RFC 1952 1f 8b),
                                 no-op when not compressed
- decodeSqsPayload(body)       - strict base64 decode then gunzip-if-magic
- decodeSnsPayload(envelope)   - JSON-parse the SNS HTTP notification,
                                 extract Message, run the SQS pipeline;
                                 falls through to a pre-extracted Message
                                 string for backward compatibility
- verifySignature(...)         - constant-time HMAC-SHA256 over the
                                 uncompressed body
- parseEvent(payload)          - JSON -> typed WHEvent

Composites (return typed WHEvent):
- verifyAndParseWebhook(rawBody, signature, secret)
- parseSqs(messageBody)
- parseSns(notificationBody)

StreamClient#verifyAndParseWebhook / parseSqs / parseSns use the
configured client secret automatically.

Backward compatibility
----------------------
StreamClient#verifyWebhook (boolean) is unchanged.

Unified error handling
----------------------
Every failure path terminates at a single class - InvalidWebhookError -
with one of four canonical messages exported as InvalidWebhookErrorMessages:
signatureMismatch / invalidBase64 / gzipFailed / invalidJson. Customers
only need one catch arm; the message identifies which mode fired.

Tests
-----
__tests__/webhook-compression.test.ts covers plain / gzip /
base64+gzip payloads, signature mismatches, malformed bytes, SNS
envelope unwrapping with and without leading whitespace, and JSON
parsing into a typed WHEvent.
@nijeesh-stream nijeesh-stream merged commit 5aa6e03 into main May 12, 2026
13 of 14 checks passed
@nijeesh-stream nijeesh-stream deleted the nijeeshjoshy/cha-3071-compress-webhook-payloads branch May 12, 2026 17:05
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants