diff --git a/src/Resend.Webhooks/Json/WebhookEventConverter.cs b/src/Resend.Webhooks/Json/WebhookEventConverter.cs index b16a78d..c7cfb1e 100644 --- a/src/Resend.Webhooks/Json/WebhookEventConverter.cs +++ b/src/Resend.Webhooks/Json/WebhookEventConverter.cs @@ -33,54 +33,74 @@ public WebhookEventConverter() /* * */ - reader.Read(); - - if ( reader.TokenType != JsonTokenType.PropertyName ) - throw new JsonException( "Expected PropertyName" ); + bool hasType = false; + bool hasCreatedAt = false; + bool hasData = false; - if ( reader.GetString() != "type" ) - throw new JsonException( "Expected 'type' property" ); + JsonElement rawData = default; - reader.Read(); - value.EventType = _wet.Read( ref reader, typeof( WebhookEventType ), options ); + // Read the 3 webhook payload properties + for ( int i = 0; i < 3; i++ ) + { + reader.Read(); + + if ( reader.TokenType != JsonTokenType.PropertyName ) + throw new JsonException( "Expected PropertyName" ); + + string? propertyName = reader.GetString(); + + if ( propertyName == "type" ) + { + if ( hasType ) + throw new JsonException( "Duplicate 'type' property" ); + + hasType = true; + + reader.Read(); + value.EventType = _wet.Read( ref reader, typeof( WebhookEventType ), options ); + } + else if ( propertyName == "created_at" ) + { + if ( hasCreatedAt ) + throw new JsonException( "Duplicate 'created_at' property" ); + + hasCreatedAt = true; + + reader.Read(); + value.MomentCreated = _utc.Read( ref reader, typeof( DateTime ), options ); + } + else if ( propertyName == "data" ) + { + if ( hasData ) + throw new JsonException( "Duplicate 'data' property" ); + + hasData = true; + + reader.Read(); + rawData = JsonElement.ParseValue( ref reader ); + } + else + { + throw new JsonException( "Invalid property name" ); + } + } - var category = value.EventType.Category(); + if ( !hasType || !hasCreatedAt || !hasData ) + throw new JsonException( "Missing required webhook properties" ); /* * */ - reader.Read(); - - if ( reader.TokenType != JsonTokenType.PropertyName ) - throw new JsonException( "Expected PropertyName" ); - - if ( reader.GetString() != "created_at" ) - throw new JsonException( "Expected 'created_at' property" ); - - reader.Read(); - value.MomentCreated = _utc.Read( ref reader, typeof( DateTime ), options ); + var category = value.EventType.Category(); /* * */ - reader.Read(); - - if ( reader.TokenType != JsonTokenType.PropertyName ) - throw new JsonException( "Expected PropertyName" ); - - if ( reader.GetString() != "data" ) - throw new JsonException( "Expected 'data' property" ); - - reader.Read(); - if ( category == WebhookEventTypeCategory.Email ) { - var t1 = typeof( EmailEventData ); - var o1 = (JsonConverter) options.GetConverter( t1 ); - - var data = o1.Read( ref reader, t1, options ); + var data = rawData.Deserialize( options ); if ( data == null ) throw new JsonException( "Expected non-null data" ); @@ -89,10 +109,7 @@ public WebhookEventConverter() } else if ( category == WebhookEventTypeCategory.Contact ) { - var t2 = typeof( ContactEventData ); - var o2 = (JsonConverter) options.GetConverter( t2 ); - - var data = o2.Read( ref reader, t2, options ); + var data = rawData.Deserialize( options ); if ( data == null ) throw new JsonException( "Expected non-null data" ); @@ -101,10 +118,7 @@ public WebhookEventConverter() } else if ( category == WebhookEventTypeCategory.Domain ) { - var t3 = typeof( DomainEventData ); - var o2 = (JsonConverter) options.GetConverter( t3 ); - - var data = o2.Read( ref reader, t3, options ); + var data = rawData.Deserialize( options ); if ( data == null ) throw new JsonException( "Expected non-null data" ); diff --git a/src/Resend/ResendExtensions.cs b/src/Resend/ResendExtensions.cs index 28d86e2..dcba146 100644 --- a/src/Resend/ResendExtensions.cs +++ b/src/Resend/ResendExtensions.cs @@ -25,8 +25,12 @@ public static WebhookEventTypeCategory Category( this WebhookEventType @event ) case WebhookEventType.EmailComplained: case WebhookEventType.EmailDelivered: case WebhookEventType.EmailDeliveryDelay: + case WebhookEventType.EmailFailed: case WebhookEventType.EmailOpened: + case WebhookEventType.EmailReceived: + case WebhookEventType.EmailScheduled: case WebhookEventType.EmailSent: + case WebhookEventType.EmailSuppressed: return WebhookEventTypeCategory.Email; default: