Skip to content

Add robust webhook verification and graceful payload parsing - #571

Draft
razor-x wants to merge 2 commits into
mainfrom
claude/sdk-webhook-union-resilience-mu1ldf
Draft

Add robust webhook verification and graceful payload parsing#571
razor-x wants to merge 2 commits into
mainfrom
claude/sdk-webhook-union-resilience-mu1ldf

Conversation

@razor-x

@razor-x razor-x commented Sep 1, 2026

Copy link
Copy Markdown
Member

Summary

This PR enhances the SDK's resilience by adding comprehensive webhook verification with proper error handling and implementing graceful degradation when parsing unexpected API responses.

Key Changes

Webhook Verification & Validation

  • Added InvalidWebhookPayloadError exception to distinguish between signature verification failures (retry-worthy) and payload parsing failures (permanent)
  • Enhanced Webhook#verify to validate that signed payloads contain required Seam event fields (event_id and event_type)
  • Added JSON parsing error handling to catch malformed payloads that pass signature verification
  • Implemented seam_event? helper to validate payload structure, supporting both symbol and string keys

Graceful Payload Parsing

  • Modified BaseResource#process_data_attributes to handle non-Hash payloads without raising, allowing resources to be created with unset attributes
  • Enhanced BaseResource#parse_datetime to catch ArgumentError and TypeError, returning nil for malformed timestamps instead of raising
  • This ensures that unexpected API response shapes don't break otherwise usable resources

Action Attempt Status Handling

  • Added ActionAttemptUnknownStatusError exception for unrecognized action attempt statuses
  • Updated ActionAttemptResolver.wait_until_resolved to raise this error when encountering unknown statuses, preventing false success claims
  • The error includes the unknown status for debugging and inherits from ActionAttemptError for backward compatibility

Dependencies

  • Added explicit require "svix/util" to ensure Svix.secure_compare is available during webhook verification

Implementation Details

  • Webhook validation happens after signature verification, so invalid payloads from genuine Seam senders are properly distinguished from tampering attempts
  • All parsing errors are caught at the attribute level, allowing partial resources to remain usable when some fields are malformed
  • The SDK now gracefully handles forward compatibility scenarios where the API adds new event types, action statuses, or error codes between releases

https://claude.ai/code/session_01M2kJ4nGaM8imZCVMEKjmXA

Seam::Webhook#verify raised NoMethodError on every call. svix/webhook calls
Svix.secure_compare but does not require the file that defines it, and this SDK
required only svix/webhook and svix/errors, so verification worked solely when
the host application happened to load the full svix gem first. Requiring
svix/util fixes it. The SDK had no webhook specs at all, which is why this went
unnoticed; there are now 11.

A signed but unreadable body no longer leaks JSON::ParserError, return nil, or
raise NoMethodError depending on its shape. It raises the new
InvalidWebhookPayloadError, kept distinct from WebhookVerificationError because
the sender is genuinely Seam and retrying can never help.

Reading a response no longer fails on the shape of the payload: a payload that
is not an object yields a resource with unset attributes rather than raising
from #each, and a malformed or non-string timestamp reads as nil rather than
raising from Time.parse at read time.

Waiting on an action attempt whose status is neither pending, success, nor error
raises the new ActionAttemptUnknownStatusError instead of returning it as a
success. Reporting success would tell the caller the action completed when the
SDK cannot tell, and the previous behavior did exactly that. It subclasses
ActionAttemptError, so existing handlers for that base keep working.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01M2kJ4nGaM8imZCVMEKjmXA
The added comments ran to roughly double the density of the code around them and
mostly restated what the line below already said. Kept the ones carrying
information the code cannot: why svix/util has to be required, why both key
shapes are accepted after symbolize_names, and why array_map needed replacing.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01M2kJ4nGaM8imZCVMEKjmXA
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