fix(growth): accept Resend webhook payloads that carry a headers array; log every outcome - #991
Merged
Merged
Conversation
…y; log every outcome Resend's webhook event log showed why recipient delivery state never left "submitted": every recipient message carries List-Unsubscribe, the job id, Bcc, and Reply-To, and Resend echoes them back as a data.headers array. The closed parser rejected the key, the route answered 400, and Resend marked the events failed with no further attempts. Founder notifications have no custom headers, which is why only those succeeded. The parser now validates the known keys and a bounded headers array, and ignores unknown keys within a bounded key count instead of failing the event. Two drifts in one day showed the closed set was hurting more than it protected. The route now logs one structured line per call with the status and reason so future attempts are visible in Vercel logs. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
blove
enabled auto-merge (squash)
September 3, 2026 21:12
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
Contributor
Contributor
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Follow-up to #989. Resend's webhook event log (
GET /webhooks/{id}/events/{event}) showed the real recipient payload: every recipient message carriesList-Unsubscribe,List-Unsubscribe-Post,X-Threadplane-Job-ID,Bcc, andReply-To, and Resend echoes them as adata.headersarray. The closed parser rejected the key, the route answered 400, and Resend marked those eventsfailedwithnext_attempt_at: null. Founder notifications carry no custom headers, which is why only those events succeeded.Change
libs/growth/src/lib/webhooks.ts: validate the known keys plus a boundedheadersarray of{name, value}; ignore unknown keys within a 32-key bound instead of failing the event.apps/website/src/app/api/webhooks/resend/route.ts: one structuredconsole.infoline per call with status, applied, and reason, so attempts are visible in Vercel runtime logs.Tests: the captured production payload shape applies; unknown keys are ignored; an unbounded key count still fails closed. Growth lint/test pass (42 webhook tests); the website route spec passes (10).
After deploy the failed events need replaying from Resend (they will not retry on their own).
🤖 Generated with Claude Code