Skip to content

Conversation

alebedev80
Copy link
Contributor

@alebedev80 alebedev80 commented Sep 17, 2025

Q A
Branch? 7.4
Bug fix? yes
New feature? yes
Deprecations? no
Issues Fix #... (if applicable)
License MIT

Summary

This PR enhances the LOX24RequestParser to provide robust SMS delivery webhook parsing with improved validation, dual status code support (DLR codes and status codes), and better error handling for SMS delivery events.

What it does

Before: The parser had basic SMS delivery webhook handling with limited validation and error handling.

After: The parser provides enhanced SMS delivery webhook processing:

  • sms.delivery & sms.delivery.dryrun - SMS delivery receipts with improved validation
  • Dual status code support: DLR codes (0-16) and transmission status codes (0, 100, etc.)
  • Better payload validation with detailed error messages
  • Non-delivery events are handled as generic RemoteEvent objects

Example Usage

Enhanced SMS Delivery Processing:

// ✅ SMS delivery with status code $deliveryPayload = [ 'id' => 'webhook-123', 'name' => 'sms.delivery', 'data' => ['id' => '123', 'status_code' => 100] ]; $deliveryEvent = $parser->parse($deliveryRequest, $secret); // Returns: SmsEvent with DELIVERED status // ✅ SMS delivery with DLR code (takes priority over status_code) $dlrPayload = [ 'id' => 'webhook-456', 'name' => 'sms.delivery', 'data' => ['id' => '456', 'dlr_code' => 1, 'status_code' => 500] ]; $dlrEvent = $parser->parse($dlrRequest, $secret); // Returns: SmsEvent with DELIVERED status (DLR code 1 overrides status 500) // ✅ Pending delivery (returns null) $pendingPayload = [ 'id' => 'webhook-789', 'name' => 'sms.delivery', 'data' => ['id' => '789', 'dlr_code' => 0] ]; $pendingEvent = $parser->parse($pendingRequest, $secret); // Returns: null (pending delivery) // ✅ Non-delivery events handled as RemoteEvent $otherPayload = [ 'id' => 'webhook-000', 'name' => 'custom.event', 'data' => ['some' => 'data'] ]; $otherEvent = $parser->parse($otherRequest, $secret); // Returns: RemoteEvent with 'custom.event' name

Key Changes

1. Improved Payload Validation

  • Enhanced validation for required webhook fields: id, name, data
  • Better error messages with specific missing field details
  • Proper array validation for data payload

2. Smart Return Types

  • SMS delivery events (sms.delivery, sms.delivery.dryrun) return SmsEvent objects with DELIVERED/FAILED status
  • Non-delivery events return RemoteEvent objects for generic webhook handling
  • Maintains null return for pending deliveries (status_code=0 or dlr_code=0/2/4)

3. Enhanced SMS Delivery Validation

// SMS delivery events validate required fields case 'sms.delivery': // Requires: payload.id, data.id, and either data.status_code OR data.dlr_code // Supports both LOX24 status codes and DLR codes // DLR codes take priority when both are present

4. Dual Status Code Support

  • DLR Codes: Priority support for LOX24's Delivery Report codes (0-16) with proper pending state handling
  • Status Codes: Fallback to transmission status codes (0, 100, 208, 400, etc.) when DLR unavailable
  • Smart Mapping: DLR codes 1→DELIVERED, 8/16→FAILED, 0/2/4→null (pending)

4. Comprehensive Testing

  • Tests for SMS delivery events with success/failure scenarios
  • DLR code and status code validation scenarios
  • Payload validation tests for required fields
  • Backward compatibility verification for existing delivery events
  • Type safety assertions (SmsEvent vs RemoteEvent)

Backward Compatibility

Fully backward compatible - existing consumers continue working without changes:

  • Same sms.delivery event handling behavior
  • Same status code and DLR code mapping logic
  • Same null return for pending messages (status_code=0, dlr_code=0/2/4)
  • Same SmsEvent object structure and methods
  • Enhanced support for both LOX24 status codes and DLR codes

Consumer Integration Example

#[AsRemoteEventConsumer('lox24_notifier')] class LOX24WebhookConsumer implements ConsumerInterface { public function consume(RemoteEvent $event): void { if ($event instanceof SmsEvent) { match($event->getName()) { SmsEvent::DELIVERED => $this->handleDelivered($event), SmsEvent::FAILED => $this->handleFailed($event), }; } else { // Handle other webhook events as generic RemoteEvent $this->handleGenericWebhook($event); } } }

Breaking Changes

⚠️ Minor breaking changes (unlikely to affect most users):

  • Parser return type signature changed from ?SmsEvent to SmsEvent|RemoteEvent|null
  • Exception messages updated with more detailed validation errors
  • Requires webhook payload to include id field (LOX24 standard)
  • Non-delivery events now return RemoteEvent instead of being rejected

Technical Implementation

  • Improved Validation: Enhanced payload validation with detailed error messages for missing fields
  • Factory Methods: Implemented specialized methods (createDeliveryEvent, createRemoteEvent, etc.)
  • Dual Code Support: Enhanced delivery event parsing to handle both LOX24 status codes and DLR codes
  • Smart Precedence: DLR codes take priority over status codes when both are present
  • Error Handling: Enhanced error messages with specific validation details
  • Null Handling: Proper handling of pending states (DLR codes 0/2/4, status code 0)

Testing

  • ✅ All existing tests pass (backward compatibility)
  • ✅ Added comprehensive test coverage for SMS delivery scenarios
  • ✅ Payload validation and error handling tests
  • ✅ DLR code and status code priority testing

This enhancement improves LOX24 SMS delivery webhook processing with better validation, dual status code support, and robust error handling while maintaining complete backward compatibility.

private function createIncomingSmsEvent(array $data, array $payload): RemoteEvent
{
if (!isset($data['from'], $data['to'], $data['text'])) {
throw new RejectWebhookException(406, 'Incoming SMS payload is malformed - missing required fields.');
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's mention the missing field.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not resolved

Copy link
Member

@fabpot fabpot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A lot of my suggestions were marked as resolved but they were not. I'm fine but can you add a small comment about why you don't think my comment was relevant/useful?

private function createIncomingSmsEvent(array $data, array $payload): RemoteEvent
{
if (!isset($data['from'], $data['to'], $data['text'])) {
throw new RejectWebhookException(406, 'Incoming SMS payload is malformed - missing required fields.');
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not resolved

'phone_number' => '+1234567890',
'fraud_score' => 0.5,
],
default => [], // For email parsing events, minimal data is acceptable
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not resolved

@alebedev80
Copy link
Contributor Author

@fabpot please review

Copy link
Member

@nicolas-grekas nicolas-grekas left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM after some CS changes

@alebedev80
Copy link
Contributor Author

@nicolas-grekas thank you! i've committed yours changes. Please check it

Copy link
Member

@fabpot fabpot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you please unify all exception messages? One sentence, no dash, mentioning that the fields are required, ...

@stof
Copy link
Member

stof commented Sep 23, 2025

Should we really add new events that are specific to the LOX24 bridge instead of having a proper abstraction in the component that can implemented by the different bridges ?
Notifier bridges are not meant to be full SDKs for a given provider. They are meant to implement the shared abstraction of the component on top of a given provider.

You could totally implement a remote event parser as part of a LOX24 SDK supporting all their features (outside Symfony core), but this does not belong to the notifier bridge IMO.

Copy link
Member

@fabpot fabpot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please, unify the error messages.

@alebedev80
Copy link
Contributor Author

@stof @nicolas-grekas i've removed LOX24 specific webhooks parsing logic. please check

@alebedev80
Copy link
Contributor Author

which conflicts i need to resolve?

@fabpot
Copy link
Member

fabpot commented Sep 24, 2025

which conflicts i need to resolve?

Maybe because the class name has changed from LOX24RequestParser to Lox24RequestParser?

@nicolas-grekas
Copy link
Member

the LOX24RequestParser file/class has been renamed to Lox24RequestParser
you'd need to fetch branch 7.4 and rebase on top of it to get the change
can you do it?

@alebedev80
Copy link
Contributor Author

Please, unify the error messages.

@fabpot done. please check this

Copy link
Member

@stof stof left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As response to my previous comment #61778 (comment), you said you reverted the logic for LOX24-specific webhooks. However, both the code and the changelog entry you added tell me that you are still adding support for the LOX24-specific webhooks that don't match the Symfony abstraction of the notifier webhooks.

Copy link
Member

@nicolas-grekas nicolas-grekas left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I suppose the PR title+description should also be synced now that the scope is reduced. LGTM otherwise, after some minor stuff.


private function createDeliveryEvent(array $data, array $payload): ?SmsEvent
{
if (!isset($data['id'])) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

just for confirmation: we already checked that id is present in payload, and id now also has to be inside payload['data'], is that correct?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@nicolas-grekas nicolas-grekas changed the title [Notifier][LOX24] Add support for all webhook event types [Notifier][LOX24] Enhance SMS delivery webhook parser with improved validation Sep 24, 2025
@nicolas-grekas nicolas-grekas changed the title [Notifier][LOX24] Enhance SMS delivery webhook parser with improved validation [Notifier][LOX24] Add support for building SmsEvent by dlr_code and RemoteEvent for other LOX24 webhook event types Sep 24, 2025
@nicolas-grekas nicolas-grekas changed the title [Notifier][LOX24] Add support for building SmsEvent by dlr_code and RemoteEvent for other LOX24 webhook event types [Notifier] Add support for building SmsEvent by dlr_code and RemoteEvent for other LOX24 webhook event types Sep 24, 2025
@nicolas-grekas
Copy link
Member

Thank you @alebedev80.

@nicolas-grekas nicolas-grekas merged commit c6e3f9b into symfony:7.4 Sep 24, 2025
11 of 12 checks passed
return new SmsEvent($eventType, $data['id'], $payload);
}

private function createRemoteEvent(string $eventName, array $payload): RemoteEvent
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's inline the code and remove this private method as it's only called once.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment