Skip to content

Navigation Menu

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Appearance settings

Commit ee47ae8

Browse filesBrowse files
committed
[WEBHOOK]: update doc
1 parent 79b3725 commit ee47ae8
Copy full SHA for ee47ae8

File tree

1 file changed

+297
-28
lines changed
Filter options

1 file changed

+297
-28
lines changed

‎webhook.rst

Copy file name to clipboardExpand all lines: webhook.rst
+297-28Lines changed: 297 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,12 @@ Webhook
55

66
The Webhook component was introduced in Symfony 6.3.
77

8-
The Webhook component is used to respond to remote webhooks to trigger actions
9-
in your application. This document focuses on using webhooks to listen to remote
10-
events in other Symfony components.
8+
Essentially, webhooks serve as event notification mechanisms, typically via HTTP POST requests, enabling real-time updates.
9+
10+
The Webhook component is used to respond to remote webhooks in order to trigger actions
11+
in your application. Additionally, it can assist in dispatching webhooks from the provider side.
12+
13+
This document provides guidance on utilizing the Webhook component within the context of a full-stack Symfony application.
1114

1215
Installation
1316
------------
@@ -16,8 +19,87 @@ Installation
1619
1720
$ composer require symfony/webhook
1821
22+
23+
Consuming Webhooks
24+
------------------
25+
26+
Consider an example of an API where it's possible to track the stock levels of various products.
27+
A webhook has been registered to notify when certain events occur, such as stock depletion for a specific product.
28+
29+
During the registration of this webhook, several pieces of information were included in the POST request,
30+
including the endpoint to be called upon the occurrence of an event, such as stock depletion for a certain product:
31+
32+
.. code-block:: json
33+
{
34+
"name": "a name",
35+
"url": "something/webhook/routing_name"
36+
"signature": "..."
37+
"events": ["out_of_stock_event"]
38+
....
39+
}
40+
41+
42+
From the perspective of the consumer application, which receives the webhook, three primary phases need to be anticipated:
43+
44+
1) Receiving the webhook
45+
46+
2) Verifying the webhook and constructing the corresponding Remote Event
47+
48+
3) Manipulating the received data.
49+
50+
Symfony Webhook, when used alongside Symfony Remote Event, streamlines the management of these fundamental phases.
51+
52+
A Single Entry Endpoint: Receive
53+
--------------------------------
54+
55+
Through the built-in :class:`WebhookController`, a unique entry point is offered to manage all webhooks
56+
that our application may receive, whether from the Twilio API, a custom API, or other sources.
57+
58+
By default, any URL prefixed with ``/webhook`` will be routed to this :class:`WebhookController`.
59+
Additionally, you have the flexibility to customize this URL prefix and rename it according to your preferences.
60+
61+
.. code-block:: yaml
62+
63+
# config/routes/webhook.yaml
64+
webhook:
65+
resource: '@FrameworkBundle/Resources/config/routing/webhook.xml'
66+
prefix: /webhook # or possible to customize
67+
68+
Additionally, you must specify the parser service responsible for analyzing and parsing incoming webhooks.
69+
It's crucial to understand that the :class:`WebhookController` itself remains provider-agnostic, utilizing
70+
a routing mechanism to determine which parser should handle incoming webhooks for analysis.
71+
72+
As mentioned earlier, incoming webhooks require a specific prefix to be directed to the :class:`WebhookController`.
73+
This prefix forms the initial part of the URL following the domain name.
74+
The subsequent part of the URL, following this prefix, should correspond to the routing name chosen in your configuration.
75+
76+
The routing name must be unique as this is what connects the provider with your
77+
webhook consumer code.
78+
79+
.. code-block:: yaml
80+
# config/webhook.yaml
81+
# e.g https://example.com/webhook/my_first_parser
82+
83+
framework:
84+
webhook:
85+
routing:
86+
my_first_parser: # routing name
87+
service: App\Webhook\ExampleRequestParser
88+
# secret: your_secret_here # optionally
89+
90+
At this point in the configuration, you can also define a secret for webhooks that require one.
91+
92+
All parser services defined for each routing name of incoming webhooks will be injected into the :class:`WebhookController`.
93+
94+
95+
A Service Parser: Verifying and Constructing the Corresponding Remote Event
96+
---------------------------------------------------------------------------
97+
98+
It's important to note that Symfony provides built-in parser services.
99+
In such cases, configuring the service name and optionally the required secret in the configuration is sufficient; there's no need to create your own parser.
100+
19101
Usage in Combination with the Mailer Component
20-
----------------------------------------------
102+
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
21103

22104
When using a third-party mailer provider, you can use the Webhook component to
23105
receive webhook calls from this provider.
@@ -94,8 +176,6 @@ component routing:
94176
};
95177
96178
In this example, we are using ``mailer_mailgun`` as the webhook routing name.
97-
The routing name must be unique as this is what connects the provider with your
98-
webhook consumer code.
99179

100180
The webhook routing name is part of the URL you need to configure at the
101181
third-party mailer provider. The URL is the concatenation of your domain name
@@ -106,7 +186,195 @@ For Mailgun, you will get a secret for the webhook. Store this secret as
106186
MAILER_MAILGUN_SECRET (in the :doc:`secrets management system
107187
</configuration/secrets>` or in a ``.env`` file).
108188

109-
When done, add a :class:`Symfony\\Component\\RemoteEvent\\RemoteEvent` consumer
189+
Usage in Combination with the Notifier Component
190+
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
191+
192+
The usage of the Webhook component when using a third-party transport in
193+
the Notifier is very similar to the usage with the Mailer.
194+
195+
Currently, the following third-party SMS transports support webhooks:
196+
197+
============ ==========================================
198+
SMS service Parser service name
199+
============ ==========================================
200+
Twilio ``notifier.webhook.request_parser.twilio``
201+
Vonage ``notifier.webhook.request_parser.vonage``
202+
============ ==========================================
203+
204+
A custom Parser
205+
~~~~~~~~~~~~~~~
206+
207+
However, if your webhook, as illustrated in the example discussed, originates from a custom API,
208+
you will need to create a parser service that extends :class:`AbstractRequestParser`.
209+
210+
This process can be simplified using a command:
211+
212+
.. code-block:: terminal
213+
214+
$ php bin/console make:webhook
215+
216+
.. tip::
217+
218+
Starting in `MakerBundle`_ ``v1.58.0``, you can run ``php bin/console make:webhook``
219+
to generate the request parser and consumer files needed to create your own
220+
Webhook.
221+
222+
.. _`MakerBundle`: https://symfony.com/doc/current/bundles/SymfonyMakerBundle/index.html
223+
224+
225+
Depending on the routing name provided to this command, which corresponds, as discussed earlier,
226+
to the second and final part of the incoming webhook URL, the command will generate the parser service responsible for parsing your webhook.
227+
228+
Additionally, it allows you to specify which RequestMatcher(s) from the HttpFoundation component should be applied to the incoming webhook request.
229+
This constitutes the initial step of your gateway process, ensuring that the format of the incoming webhook is validated before proceeding to its thorough analysis.
230+
231+
Furthermore, the command will create the :class:`RemoteEventConsumer`, which manages the remote event returned by the parser.
232+
233+
Moreover, this command will automatically update the previously discussed configuration with the webhook's routing name.
234+
This ensures that not only are the parser and consumer generated, but also that the configuration is seamlessly updated::
235+
236+
// src/Webhook/ExampleRequestParser.php
237+
final class ExampleRequestParser extends AbstractRequestParser
238+
{
239+
protected function getRequestMatcher(): RequestMatcherInterface
240+
{
241+
return new ChainRequestMatcher([
242+
new IsJsonRequestMatcher(),
243+
new MethodRequestMatcher('POST'),
244+
new HostRequestMatcher('regex'),
245+
new ExpressionRequestMatcher(new ExpressionLanguage(), new Expression('expression')),
246+
new PathRequestMatcher('regex'),
247+
new IpsRequestMatcher(['127.0.0.1']),
248+
new PortRequestMatcher(443),
249+
new SchemeRequestMatcher('https'),
250+
]);
251+
}
252+
253+
/**
254+
* @throws JsonException
255+
*/
256+
protected function doParse(Request $request, #[\SensitiveParameter] string $secret): ?RemoteEvent
257+
{
258+
// Adapt or replace the content of this method to fit your need.
259+
// e.g Validate the request against $secret and/or Validate the request payload
260+
// and/or Parse the request payload and return a RemoteEvent object or throw an exception
261+
262+
return new RemoteEvent(
263+
$payload['name'],
264+
$payload['id'],
265+
$payload,
266+
);
267+
}
268+
}
269+
270+
271+
Now, imagine that in your case, you receive a notification of a product stock outage, and the received JSON contains details about the affected product and the severity of the outage.
272+
Depending on the specific product and the severity of the stock outage, your application can trigger different remote events.
273+
274+
For instance, you might define ``HighPriorityStockRefillEvent``, ``MediumPriorityStockRefillEvent`` and ``LowPriorityStockRefillEvent``.
275+
276+
277+
By implementing the :class:`PayloadConverterInterface` and its :method:`Symfony\\Component\\RemoteEvent\\PayloadConverterInterface::convert` method, you can encapsulate all the business logic
278+
involved in creating the appropriate remote event. This converter will be invoked by your parser.
279+
280+
For inspiration, you can refer to :class:`MailGunPayloadConverter`::
281+
282+
// src/Webhook/ExampleRequestParser.php
283+
final class ExampleRequestParser extends AbstractRequestParser
284+
{
285+
protected function getRequestMatcher(): RequestMatcherInterface
286+
{
287+
...
288+
}
289+
290+
/**
291+
* @throws JsonException
292+
*/
293+
protected function doParse(Request $request, #[\SensitiveParameter] string $secret): ?RemoteEvent
294+
{
295+
// Adapt or replace the content of this method to fit your need.
296+
// e.g Validate the request against $secret and/or Validate the request payload
297+
// and/or Parse the request payload and return a RemoteEvent object or throw an exception
298+
299+
try {
300+
return $this->converter->convert($content['...']);
301+
} catch (ParseException $e) {
302+
throw new RejectWebhookException(406, $e->getMessage(), $e);
303+
}
304+
}
305+
}
306+
307+
// src/RemoteEvent/ExamplePayloadConverter.php
308+
final class ExamplePayloadConverter implements PayloadConverterInterface
309+
{
310+
public function convert(array $payload): AbstractPriorityStockRefillEvent
311+
{
312+
...
313+
314+
if (....) {
315+
$event = new HighPriorityStockRefillEvent($name, $payload['id]', $payload])
316+
} elseif {
317+
$event = new MediumPriorityStockRefillEvent($name, $payload['id]', $payload])
318+
} else {
319+
$event = new LowPriorityStockRefillEvent($name, $payload['id]', $payload])
320+
}
321+
322+
....
323+
324+
return $event;
325+
}
326+
}
327+
328+
From this, we can see that the Remote Event component is highly beneficial for handling webhooks.
329+
It enables you to convert the incoming webhook data into validated objects that can be efficiently manipulated and utilized according to your requirements.
330+
331+
Remote Event Consumer: Handling and Manipulating The Received Data
332+
------------------------------------------------------------------
333+
334+
It is important to note that when the incoming webhook is processed by the :class:`WebhookController`, you have the option to handle the consumption of remote events asynchronously.
335+
Indeed, this can be configured using a bus, with the default setting pointing to the Messenger component's default bus.
336+
For more details, refer to the :doc:`Symfony Messenger </components/messenger>` documentation
337+
338+
339+
Whether the remote event is processed synchronously or asynchronously, you'll need a consumer that implements the :class:`ConsumerInterface`.
340+
If you used the command to set this up, it was created automatically
341+
342+
.. code-block:: terminal
343+
344+
$ php bin/console make:webhook
345+
346+
Otherwise, you'll need to manually add it with the ``AsRemoteEventConsumer`` attribute which will allow you to designate this class as a :class:`ConsumerInterface`,
347+
making it recognizable to the Remote Event component so it can pass the converted object to it.
348+
Additionally, the name passed to your attribute is critical; it must match the configuration entry under routing that you specified in the ``webhook.yaml`` file, which in your case is ``my_first_parser```.
349+
350+
In the :method:`Symfony\\Component\\RemoteEvent\\Consumer\\ConsumerInterface::consume` method,
351+
you can access your object containing the event data that triggered the webhook, allowing you to respond appropriately.
352+
353+
For example, you can use Mercure to broadcast updates to clients of the hub, among other actions ...::
354+
355+
// src/Webhook/ExampleRequestParser.php
356+
#[AsRemoteEventConsumer('my_first_parser')] # routing name
357+
final class ExampleWebhookConsumer implements ConsumerInterface
358+
{
359+
public function __construct()
360+
{
361+
}
362+
363+
public function consume(RemoteEvent $event): void
364+
{
365+
// Implement your own logic here
366+
}
367+
}
368+
369+
370+
If you are using it alongside other components that already include built-in parsers,
371+
you will need to configure the settings (as mentioned earlier) and also create your own consumer.
372+
This is necessary because it involves your own business logic and your specific reactions to the remote event(s) that may be received from the built-in parsers.
373+
374+
Usage in Combination with the Mailer Component
375+
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
376+
377+
You can add a :class:`Symfony\\Component\\RemoteEvent\\RemoteEvent` consumer
110378
to react to incoming webhooks (the webhook routing name is what connects your
111379
class to the provider).
112380

@@ -122,7 +390,7 @@ events::
122390
use Symfony\Component\RemoteEvent\RemoteEvent;
123391

124392
#[AsRemoteEventConsumer('mailer_mailgun')]
125-
class WebhookListener implements ConsumerInterface
393+
class MailerWebhookConsumer implements ConsumerInterface
126394
{
127395
public function consume(RemoteEvent $event): void
128396
{
@@ -148,19 +416,7 @@ events::
148416
}
149417

150418
Usage in Combination with the Notifier Component
151-
------------------------------------------------
152-
153-
The usage of the Webhook component when using a third-party transport in
154-
the Notifier is very similar to the usage with the Mailer.
155-
156-
Currently, the following third-party SMS transports support webhooks:
157-
158-
============ ==========================================
159-
SMS service Parser service name
160-
============ ==========================================
161-
Twilio ``notifier.webhook.request_parser.twilio``
162-
Vonage ``notifier.webhook.request_parser.vonage``
163-
============ ==========================================
419+
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
164420

165421
For SMS webhooks, react to the
166422
:class:`Symfony\\Component\\RemoteEvent\\Event\\Sms\\SmsEvent` event::
@@ -189,13 +445,26 @@ For SMS webhooks, react to the
189445
}
190446
}
191447

192-
Creating a Custom Webhook
193-
-------------------------
194448

195-
.. tip::
449+
Providing Webhooks
450+
------------------
196451

197-
Starting in `MakerBundle`_ ``v1.58.0``, you can run ``php bin/console make:webhook``
198-
to generate the request parser and consumer files needed to create your own
199-
Webhook.
452+
Symfony Webhook and Symfony Remote Event, when combined with Symfony Messenger, are also useful for APIs responsible for dispatching webhooks.
200453

201-
.. _`MakerBundle`: https://symfony.com/doc/current/bundles/SymfonyMakerBundle/index.html
454+
For instance, you can utilize the specific :class:`SendWebhookMessage` and :class:`SendWebhookHandler` provided to dispatch the message either synchronously or asynchronously using the Symfony Messenger component.
455+
456+
The SendWebhookMessage takes a :class:`Subscriber` as its first argument, which includes the destination URL and the mandatory secret.
457+
If the secret is missing, an exception will be thrown.
458+
459+
As a second argument, it expects a :class:`RemoteEvent` containing the webhook name, the ID, and the payload, which is the substantial information you wish to communicate.
460+
461+
The :class:`SendWebhookHandler` configures the headers, the body of the request, and finally sign the headers before making an HTTP request to the specified URL using Symfony's HttpClient component::
462+
463+
$subscriber = new Subscriber($urlCallback, $secret);
464+
465+
$event = new Event(‘name.event, ‘1’, […]);
466+
467+
$this->bus->dispatch(new SendWebhookMessage($subscriber, $event));
468+
469+
470+
However, you also have the flexibility to define your own message, handler, or custom mechanism, and process it either synchronously or asynchronously.

0 commit comments

Comments
0 (0)
Morty Proxy This is a proxified and sanitized view of the page, visit original site.