diff --git a/src/SocketLabs.EventWebhooks.Extensions/Controllers/ParsedMessagesController.cs b/src/SocketLabs.EventWebhooks.Extensions/Controllers/ParsedMessagesController.cs index 8fa24ee..25cc5a6 100644 --- a/src/SocketLabs.EventWebhooks.Extensions/Controllers/ParsedMessagesController.cs +++ b/src/SocketLabs.EventWebhooks.Extensions/Controllers/ParsedMessagesController.cs @@ -41,13 +41,19 @@ public async Task Post(WebhookEventBase webhookEvent, string id) { webhookEvent.WebhookEndpointName = id; - Task result = webhookEvent switch + Task? result = webhookEvent switch { MessageParsedEvent eventItem => ProcessEvent(eventItem), ValidationEvent eventItem => ProcessEvent(eventItem), - _ => throw new InvalidOperationException("Unable to convert event type.") + _ => null }; + if (result == null) + { + _logger.LogError("Unable to convert event type: {EventType} for MessageId: {MessageId}", webhookEvent.GetType().Name, webhookEvent.MessageId); + return BadRequest($"Unknown event type: {webhookEvent.GetType().Name}"); + } + await result; } catch (Exception ex) diff --git a/src/SocketLabs.EventWebhooks.Extensions/Controllers/WebhookEventsController.cs b/src/SocketLabs.EventWebhooks.Extensions/Controllers/WebhookEventsController.cs index 308cd51..84b5d34 100644 --- a/src/SocketLabs.EventWebhooks.Extensions/Controllers/WebhookEventsController.cs +++ b/src/SocketLabs.EventWebhooks.Extensions/Controllers/WebhookEventsController.cs @@ -3,10 +3,11 @@ using Microsoft.Extensions.Options; using SocketLabs.EventWebhooks.Extensions.Configuration; using SocketLabs.EventWebhooks.Extensions.Models.Events; +using SocketLabs.EventWebhooks.Extensions.Models.Inbound; namespace SocketLabs.EventWebhooks.Extensions.Controllers { - [Route("api/v1/[controller]")] + [Route("api/v1/[controller]/{id}")] [ApiController] public class WebhookEventsController : ControllerBase { @@ -24,44 +25,69 @@ IOptionsMonitor options _logger = logger; _options = options.CurrentValue; } - + [HttpPost] - [Route("{id}")] - public async Task Post(WebhookEventBase webhookEvent, string id) + public async Task Post(WebhookEventBatch? webhookEvents, string id) { - if (!_options.TryGetWebhook(id, out var endpoint) || endpoint?.SecretKey != webhookEvent.SecretKey) - { + if (webhookEvents == null) return BadRequest(); + + if(!_options.TryGetWebhook(id, out var endpoint) || endpoint==null) return Unauthorized(); - } - try - { - webhookEvent.WebhookEndpointName = id; + // Collect all events with mismatched secret keys + var mismatchedEvents = webhookEvents + .Where(e => e.SecretKey != endpoint.SecretKey) + .Select(e => e.MessageId ?? "(no MessageId)") + .ToList(); - Task result = webhookEvent switch + if (mismatchedEvents.Any()) + { + _logger.LogWarning("SecretKey mismatch for events: {EventIds} on endpoint {Endpoint}", string.Join(", ", mismatchedEvents), id); + return Unauthorized(new { - ComplaintEvent eventItem => ProcessEvent(eventItem), - DeferredEvent eventItem => ProcessEvent(eventItem), - EngagementEvent eventItem => ProcessEvent(eventItem), - FailedEvent eventItem => ProcessEvent(eventItem), - QueuedEvent eventItem => ProcessEvent(eventItem), - SentEvent eventItem => ProcessEvent(eventItem), - ValidationEvent eventItem => ProcessEvent(eventItem), - _ => throw new InvalidOperationException("Unable to convert event type.") - }; - - await result; + error = "One or more events have an invalid SecretKey.", + eventIds = mismatchedEvents + }); } - catch (Exception ex) + + foreach (var webhookEvent in webhookEvents) { - _logger.LogError(ex, "Unable to process webhook event."); + try + { + webhookEvent.WebhookEndpointName = id; + + Task? result = webhookEvent switch + { + ComplaintEvent eventItem => ProcessEvent(eventItem), + DeferredEvent eventItem => ProcessEvent(eventItem), + EngagementEvent eventItem => ProcessEvent(eventItem), + FailedEvent eventItem => ProcessEvent(eventItem), + QueuedEvent eventItem => ProcessEvent(eventItem), + SentEvent eventItem => ProcessEvent(eventItem), + ValidationEvent eventItem => ProcessEvent(eventItem), + _ => null + }; + + if (result == null) + { + _logger.LogError("Unable to convert event type: {EventType} for MessageId: {MessageId}", + webhookEvent.GetType().Name, webhookEvent.MessageId); + return BadRequest($"Unknown event type: {webhookEvent.GetType().Name}"); + } - return BadRequest(); + await result; + } + catch (Exception ex) + { + _logger.LogError(ex, "Unable to process webhook event."); + return BadRequest(); + } } return Ok(); } + private async Task ProcessEvent(ComplaintEvent webhookEvent) { webhookEvent.Type = "Complaint"; diff --git a/src/SocketLabs.EventWebhooks.Extensions/Models/Events/WebhookEventBase.cs b/src/SocketLabs.EventWebhooks.Extensions/Models/Events/WebhookEventBase.cs index 89afef1..5fdecb6 100644 --- a/src/SocketLabs.EventWebhooks.Extensions/Models/Events/WebhookEventBase.cs +++ b/src/SocketLabs.EventWebhooks.Extensions/Models/Events/WebhookEventBase.cs @@ -11,6 +11,10 @@ namespace SocketLabs.EventWebhooks.Extensions.Models.Events //[JsonDerivedType(typeof(QueuedEvent), typeDiscriminator: "Queued")] //[JsonDerivedType(typeof(DeferredEvent), typeDiscriminator: "Deferred")] + [JsonConverter(typeof(SingleOrArrayConverter))] + public class WebhookEventBatch : List + {} + [JsonConverter(typeof(WebhookEventConverter))] public abstract class WebhookEventBase { diff --git a/src/SocketLabs.EventWebhooks.Extensions/Models/Events/WebhookEventConverter.cs b/src/SocketLabs.EventWebhooks.Extensions/Models/Events/WebhookEventConverter.cs index 8fb16ea..1629264 100644 --- a/src/SocketLabs.EventWebhooks.Extensions/Models/Events/WebhookEventConverter.cs +++ b/src/SocketLabs.EventWebhooks.Extensions/Models/Events/WebhookEventConverter.cs @@ -4,6 +4,38 @@ namespace SocketLabs.EventWebhooks.Extensions.Models.Events { + internal class SingleOrArrayConverter : JsonConverter + { + public override WebhookEventBatch? Read( + ref Utf8JsonReader reader, + Type typeToConvert, + JsonSerializerOptions options) + { + switch (reader.TokenType) + { + case JsonTokenType.Null: + return null; + case JsonTokenType.StartArray: + var list = new WebhookEventBatch(); + while (reader.Read()) + { + if (reader.TokenType == JsonTokenType.EndArray) + break; + list.Add(JsonSerializer.Deserialize(ref reader, options)); + } + return list; + default: + return [JsonSerializer.Deserialize(ref reader, options)]; + } + } + + public override void Write( + Utf8JsonWriter writer, + WebhookEventBatch? objectToWrite, + JsonSerializerOptions options) => + JsonSerializer.Serialize(writer, objectToWrite, objectToWrite.GetType(), options); + } + internal class WebhookEventConverter : JsonConverter { public override WebhookEventBase? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) diff --git a/src/SocketLabs.EventWebhooks.Extensions/SocketLabs.EventWebhooks.Extensions.csproj b/src/SocketLabs.EventWebhooks.Extensions/SocketLabs.EventWebhooks.Extensions.csproj index 660822c..24d8789 100644 --- a/src/SocketLabs.EventWebhooks.Extensions/SocketLabs.EventWebhooks.Extensions.csproj +++ b/src/SocketLabs.EventWebhooks.Extensions/SocketLabs.EventWebhooks.Extensions.csproj @@ -14,7 +14,7 @@ icon.png README.md LICENSE.md - + 13.0