Consuming webhooks
Beppe Catanese
Posted on February 2, 2023
Webhooks are an essential feature of the Adyen Platform. Payment flows often depend on external factors, like a third-party authorization. There are also events outside the control of the application that integrate with Adyen, for example triggered by customers (such as chargeback requests) or by administration functions (like the availability of a new report).
This is why we have created a sophisticated framework that caters to all those scenarios, addressing flexibility (how webhooks are set up and managed), reliability (retry and event logging), and consistency (events are delivered as they happen).
In this article..
..find out how to set up, validate, and consume webhooks. Learn about best practices for an optimal integration.
You can also check out the “Creating and consuming notification webhooks” video on our AdyenDevs YouTube channel.
What is a webhook?
A webhook is an HTTP-based push mechanism that enables Adyen to send real time events to your application. There are multiple events that might be important in your workflow, for example payment status updates and modifications or changes to the company account and store setup.
The framework allows businesses and platforms to subscribe to the events that are relevant and configure the additional data that should be delivered with the event.
An example
Let's look at a payment flow using the Adyen Drop-in: with this UI component, you can integrate a Checkout component in your web application that supports various payment methods and can perform the actual payment flow.
After the initialization (in the case of the drop-in, it is the creation of a session), the component takes care of everything, from displaying the available payment methods to capturing the data entered by the shopper and executing the payment.
What then happens is that the final payment status is later notified (confirmed) asynchronously, after a short delay, via a webhook.
Do I need a webhook?
You do. In case of a payment, the confirmation might come after a significant delay. In some local payment methods (for instance iDEAL in the Netherlands), the final outcome of the payment might take several hours to complete.
There are also a variety of events triggered by the platform that are important and which your back office (and CRM) should be informed about: we have already mentioned the availability of a new report but it is also crucial to stay up to date with changes to merchant data, updates on the terminal fleet configuration, and platform onboarding milestones.
Last but not least, the webhook framework provides features like automated retries, system notifications, and logging that help manage errors and monitor inconsistencies.
Developers in action
Let’s dig into technical details that are relevant to developers, shall we?
In the next sections we will be looking at how to:
- Create and validate a webhook
- Secure a webhook
- Run the sample application
Create and validate a webhook
There are 2 ways to create a webhook: via the Customer Area web application and through the Management API.
In this article, we focus on the Customer Area to perform the setup (so we can better understand the relevant fields and options). You can check out our previous blog to explore how to use the Management API to automate configuration and workflows.
Customer Area
Log in to the Customer Area and access the Developers-> Webhook screen (your account must have the Merchant Technical Integrator or Merchant Admin role) and create a new webhook.
In this example, we set up a Standard notification: choose a name (Description) and enter a valid URL in the Server configuration field (this is the URL of the application that will be processing the incoming webhook event).
In this setup, we are going to use webhook.site (), a tool that helps to validate and inspect incoming webhooks without the need to expose your application or server to the internet. Grab the URL generated by webhook.site, paste it into Server configuration and click **Apply*.
(*) Other websites offer similar options
Validate the webhook
Even before creating the webhook, it is already possible to perform the validation. Click on Test Configuration and choose which Event to send: the JSON payload – which will be delivered – is displayed and can be edited (if needed).
Go ahead and test: webhook.site will show you (in real time) the incoming notification with all the relevant details (like HTTP request headers and request body) while in the Customer Area you can see the HTTP response code and body received back.
HTTP 200 but test fails
The validation however did not entirely succeed. We have received a response with HTTP status code 200 but the Customer Area warning is pretty clear: the body of the HTTP response is not what we expected.
We can address this in webhook.site: find the Edit menu where it is possible to define the body of the HTTP response. Edit the webhook configuration to return the string [accepted].
When re-running the validation we can see that the warning has disappeared and the test is now successful.
Note for developers: webhook responses must return HTTP status code 200 and a body with the string ‘[accepted]’ when this is successfully consumed.
Secure a webhook
Complete the webhook configuration, setting the recommended security settings via:
- Basic Authentication
- HMAC key
Note: we also recommend configuring HTTPS using TLS 1.3 for the endpoint that received the webhook events.
Basic Authentication
Setting up Basic Authentication for the webhook is very important: you want to authenticate the incoming HTTP requests and confirm they are sent by Adyen.
Define the Username and Password: those will be encoded (base64) and passed in the Authorization header. It is the responsibility of your server (or application) to verify the credentials when an incoming notification arrives.
Basic Authentication is one of the oldest and simplest ways to perform authentication over HTTP. It is supported by pretty much all web servers, API gateways, and HTTP-facing software: the recommendation is to use one of the proven products or open source packages instead of implementing this at application level.
HMAC key
Generate the HMAC key which will be used by Adyen to sign the webhook event: the signature will be included in the JSON payload and, again, it is the responsibility of your application to verify it is valid.
This security setting is also essential as it guarantees that the webhook comes from Adyen and the payload has not been altered by unauthorized parties.
Run the sample application
It is finally time to look at the code and how the webhook is consumed.
Visit the Github repository of the Adyen NodeJS sample application: it implements a Checkout screen that supports various payment methods as well as an example of the endpoint consuming the webhook.
The README file explains what you need to do to run the application on Gitpod, a developer-friendly cloud IDE (Integrated Development Environment):
- Get hold of the Adyen API keys
- Configure Gitpod environment variables
- Launch the workspace
- Configure the webhook (URL and HMAC key)
Note to developers: pay attention to the order of the steps above. Gitpod generates a new URL for each workspace. Therefore it is necessary to launch it first then copy the generated URL. Use the Gitpod URL to configure the webhook (field Server Configuration) in the Customer Area. Don’t forget to generate the HMAC key (necessary to validate the webhook payload) and store it as a Gitpod environment variable (ADYEN_HMAC_KEY).
Webhook endpoint
The Sample Application provides an implementation of the endpoint that will receive and consume the webhook event.
First take a look at the JSON payload schema to understand the structure of the payload. Here is an example with the most relevant attributes of the AUTHORISATION event:
{
"live": "false",
"notificationItems": [
{
"NotificationRequestItem": {
"additionalData": {
"recurring.recurringDetailReference": "123",
"recurring.shopperReference": "xyz",
“hmacSignature”: “ab1323…..”
},
"amount": {
"currency": "EUR",
"value": 1000
},
"eventCode": "AUTHORISATION",
"eventDate": "2022-12-01T01:00:00+01:00",
"merchantAccountCode": "YOUR_MERCHANT_ACCOUNT",
"merchantReference": "YOUR_MERCHANT_REFERENCE",
"paymentMethod": "ach",
"pspReference": "YOUR_PSP_REFERENCE",
"operations" : [],
"success": "true"
}
}
]
}
The payload includes an array of NotificationItems
: the array will always include a single element. Developers should validate the hmacSignature and process the item.
Fortunately all our libraries include the functions to perform this type of validation, so the step is straightforward.
app.post("/api/webhooks/notifications", async (req, res) => {
// YOUR_HMAC_KEY from the Customer Area
const hmacKey = process.env.ADYEN_HMAC_KEY;
// Notification Request JSON
const body = req.body;
const items = body.notificationItems
// Fetch single event
const notification = items[0].NotificationRequestItem
console.log(eventCode: ’ + notification.eventCode);
// validate HMAC signature
if(validator.validateHMAC(notification, hmacKey)) {
// process payload asynchronously
const result = consumePayload(notification);
res.send('[accepted]')
} else {
// invalid signature
res.status(401).send('Invalid HMAC signature');
}
}
function consumePayload() {
// add to queue or DB
}
Best practices
Here are several recommendations and tips to consider for getting the best out of the webhook framework.
Security
This was already discussed above, but it is really important so let’s recap once more.
Use both Basic Authentication (authenticate incoming requests) and HMAC (validate signature of the payload). Don’t forget to leverage TLS 1.3, the most modern security protocol.
The webhook endpoint doesn’t need to include any parameter, especially your API key or other tokens. Usually a URL without query string should be enough:
https://{your_server}/{path}
Ie https://example.com/webhook/api
Manage duplicates and expect delays
It’s unusual but webhook events might be delivered more than once. Therefore you should handle duplicates in your design.
The duplicates have the same ‘eventCode’ and ‘pspReference’: use this unique combination to identify when a second event is delivered and update the information in your system accordingly. For example the Adyen Revenue Accelerate supports retrying payments that have failed but eventually become authorized after another attempt.
Webhook events are delivered with a certain delay (few seconds to minutes). We understand this is crucial in the case of payment authorizations: we strive to minimize the delay but often that depends on external factors (response from third-party systems).
Retry
Each webhook delivery expects an acknowledgement that will mark it as successful.
When this is not the case, the Adyen Platform will resend the event later. The first retry occurs after a few minutes but the following attempts (until the acknowledgement is received) are scheduled with an increasing delay.
Make sure your endpoint implementation responds correctly and monitor errors on your side.
Consume payload asynchronously
Developers might design their application logic to process the webhook events as they come, within the same application thread. For example:
app.post("/api/webhooks/notifications", async (req, res) => {
// Notification Request JSON
const items = req.body.notificationItems
// Fetch single event
const notification = items[0].NotificationRequestItem
if( validator.validateHMAC(notification, hmacKey) ) {
// apply business logic
// send ack
res.send('[accepted]')
}
This is an approach we do not recommend. The complexity of the application logic (business rules as well as integration with other components) or the duration of certain operations (downloading large reports) might take a long time and can cause the timeout of the HTTP request.
In situations like these, the webhook event is marked as failed and will be resent later.
The best practice is to consume and process the payload asynchronously: the webhook is marked successful immediately after validating the HMAC signature while your backend takes care of consuming and processing the event.
app.post("/api/webhooks/notifications", async (req, res) => {
if( validator.validateHMAC(notification, hmacKey) ) {
// process payload asynchronously
const result = consumePayload(notification);
res.send('[accepted]')
} else {
// invalid signature
res.status(401).send('Invalid HMAC signature');
}
}
function consumePayload() {
// add to queue or DB
}
Conclusion
Webhooks are an essential feature of the Adyen Platform and we try our best to provide exhaustive documentation, code samples, and relevant technical content.
Does the asynchronous nature of the webhook add complexity to your architecture? Do you have suggestions or requests to improve your developer experience? Reach out to @AdyenDevs and let’s get started.
Posted on February 2, 2023
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.