When integrating the Mollie API, you can stay informed and automatically receive updates about changes to your Mollie account through webhooks. These changes, known as events, are triggered by specific actions related to various resources - for example, the creation of a new payment link or the blocking of your profile.
In essence, you set up a webhook by providing a URL that we can access. Every time something changes on Mollie's side, we can then issue an HTTP POST request to your webhook URL. The request will contain an event describing what changed.
For example, when your customer completes a payment, using webhooks we can proactively inform you the payment was moved to the 'paid' status.
What’s new
-
New event types to offer a broader context for your business processes (e.g. payment creation, invoice creation, onboarding status changes etc).
-
Updated API endpoints to improve the way you receive updates:
- Updated Webhooks API allowing you to permanently subscribe to specific event types, compared to 'one-off’ webhooks fired by each payment.
- Webhook Events API allowing you to retroactively inspect past events sent to you via webhooks and enable retrieving detailed information about each individual triggered event.
-
Flexible payload delivery: choose between a full payload (snapshot) that gives detailed context data including full resource payload and metadata (you always get the full payload via public API) or a simple payload that returns basic information, such as identifiers and event type that triggered it (when using Dashboard or App).
//Full payload example { "resource": "event", "id": "event_GvJ8WHrp5isUdRub9CJyH", "type": "payment-link.paid", "entityId": "pl_qng5gbbv8NAZ5gpM5ZYgx", "createdAt": "2024-12-16T15:57:04.0Z", "_embedded": { "entity": { "id": "pl_qng5gbbv8NAZ5gpM5ZYgx", "profileId": "pfl_D96wnsu869", "mode": "live", "description": "Bicycle tires", "amount": { "currency": "EUR", "value": "24.95" }, "archived": false, "redirectUrl": "https://webshop.example.org/thanks", "createdAt": "2021-03-20T09:29:56.0Z", "expiresAt": "2023-06-06T11:00:00.0Z", "reusable": true, "_links": { "self": { "href": "/v2/payment-links/pl_qng5gbbv8NAZ5gpM5ZYgx", "type": "application/hal+json" }, "paymentLink": { "href": "https://payment-link.dev.mollielabs.com/payment/qng5gbbv8NAZ5gpM5ZYgx", "type": "application/hal+json" }, "documentation": { "href": "https://docs.mollie.com/reference/create-payment-link", "type": "text/html" } } } }, "_links": { "self": { "href": "https://api.mollie.com/v2/events/event_GvJ8WHrp5isUdRub9CJyH", "type": "application/hal+json" }, "documentation": { "href": "https://docs.mollie.com/guides/webhooks", "type": "text/html" }, "payment-link": { "href": "/v2/payment-links/pl_qng5gbbv8NAZ5gpM5ZYgx", "type": "application/hal+json" } } }//Simple payload example { "resource": "event", "id": "event_GvJ8WHrp5isUdRub9CJyH", "type": "payment-link.paid", "entityId": "pl_qng5gbbv8NAZ5gpM5ZYgx", "createdAt": "2024-12-16T15:59:04.0Z", "_links": { "self": { "href": "https://api.mollie.com/v2/events/event_GvJ8WHrp5isUdRub9CJyH", "type": "application/hal+json" }, "documentation": { "href": "https://docs.mollie.com/guides/webhooks", "type": "text/html" }, "payment-link": { "href": "/v2/payment-links/pl_qng5gbbv8NAZ5gpM5ZYgx", "type": "application/hal+json" } } }
The full payload is the recommended approach, however you must put in place the appropriate security measures to ensure the security, confidentiality, integrity and authenticity of the webhook payload.
Check out the Best practices article for a complete explanation of possible risks of adhering to this option without appropriate mitigations.
- Webhook signing with a SHA256 (HMAC) signature to ensure that payloads are verified and secure, adhering to industry standards.
- Subscribing to events rather than polling an API to check for updates or new data: the Webhooks API is now available both via Mollie Public API and also in your Mollie Dashboard.
With the updated structure there is also a change in how responsibilities are managed within the new system. Below you can find what this means for you and how it changes your day to day operations.
Overview
To start receiving webhooks, you need to:
- Create a public URL in your back-end that will be handling the HTTP requests.
- Set up webhook signature verification.
- Configure the webhook, either via the Mollie dashboard or via the API.
The below guide will take you through each step in detail.
Setting up webhooks
1. Create a webhook endpoint
Set up a publicly accessible URL that can accept our HTTP POST requests.
Live mode requires HTTPSThere are no security requirements for
testmode. However, in order to receivelivemode events, the URL needs to be secured with an up-to-date HTTPS connection.
The requests we send to the endpoint will contain an event object, describing which entity was changed and how.
The endpoint should quickly respond with a success status such as 200. Avoid adding complex logic to the handler that may slow down the response.
If your endpoint takes too long to respond (>15s), we may consider the delivery a failure and try again, using the retry schema described in Retry schema.
Example event
An example event object for a payment-link.paid event for payment link pl_qng5gbbv8NAZ5gpM5ZYgx is shown below. A snapshot of the full payment link object is available as an embed.
{
"resource": "event",
"id": "event_GvJ8WHrp5isUdRub9CJyH",
"type": "payment-link.paid",
"entityId": "pl_qng5gbbv8NAZ5gpM5ZYgx",
"createdAt": "2024-12-09T14:02:31.0Z",
"_embedded": {
"entity": {
"id": "pl_qng5gbbv8NAZ5gpM5ZYgx",
"profileId": "pfl_D96wnsu869",
"mode": "live",
"description": "Bicycle tires",
"amount": {
"currency": "EUR",
"value": "24.95"
},
"archived": false,
"redirectUrl": "https://webshop.example.org/thanks",
"createdAt": "2021-03-20T09:29:56.0Z",
"expiresAt": "2023-06-06T11:00:00.0Z",
"reusable": true,
"_links": {
"self": {
"href": "/v2/payment-links/pl_qng5gbbv8NAZ5gpM5ZYgx",
"type": "application/hal+json"
},
"paymentLink": {
"href": "https://payment-link.dev.mollielabs.com/payment/qng5gbbv8NAZ5gpM5ZYgx",
"type": "application/hal+json"
},
"documentation": {
"href": "https://docs.mollie.com/reference/create-payment-link",
"type": "text/html"
}
}
}
},
"_links": {
"self": {
"href": "https://api.mollie.com/v2/events/event_GvJ8WHrp5isUdRub9CJyH",
"type": "application/hal+json"
},
"documentation": {
"href": "https://docs.mollie.com/guides/webhooks",
"type": "text/html"
},
"payment-link": {
"href": "/v2/payment-links/pl_qng5gbbv8NAZ5gpM5ZYgx",
"type": "application/hal+json"
}
}
}2. Set up webhook signature verification
Generate a secret string and store it securely in your application.
Each event we deliver will contain a X-Mollie-Signature header. This signature is generated using the signing secret and you must verify it to protect yourself against webhook spoofing.
We recommend using the official Mollie libraries to verify the signature.
Manual signature verification
We recommend using the official libraries instead of manual verification. However, if you prefer to validate it manually, follow the next steps.
The X-Mollie-Signature header attached to each request will look like sha256=4a4c6f3ed4d15fee87ad44e07a7fa9b8.
The signature is an HMAC of the webhook request body, using the SHA-256 hashing algorithm, and the signing secret you provided to us.
To verify its authenticity:
- Remove the
sha256=prefix from the signature header, leaving you with the raw signature value. - Calculate a SHA-256-based HMAC using the unaltered
POSTbody of the webhook request as the 'message', and the signing secret you provided to us. - Compare our signature (from step 1) with the hash you generated (in step 2). Reject the request if these signatures do not match.
3. Configure the URL in your Mollie account
Now that you have a live webhook URL as well as a signing secret, you can set up your webhook in your Mollie account. You can do so either via the Mollie dashboard, or via the Hooks API.
You will be asked to provide:
- The webhook URL.
- Which event types you would like to subscribe to — e.g.
payment-link.paid. - The signing secret — see step 2.
Event types
We currently support webhooks for the following events:
Global Events
These events are publicly available and supported across all accounts.
| Event type | Permissions Required | Description |
|---|---|---|
payment-link.paid | payment-links.read | A payment link moved to the paid status. |
balance-transaction.created | balances.read | Occurs when a balance transaction is created to add to your available balance. It currently only supports positive amounts and non-captured payments. |
sales-invoice.created | invoices.read | Occurs when a sales invoice has been created. |
sales-invoice.issued | invoices.read | Occurs when a sales invoice has been issued. |
sales-invoice.canceled | invoices.read | Occurs when a sales invoice has been canceled. |
sales-invoice.paid | invoices.read | Occurs when a sales invoice has been paid. |
business-account-transfer.requested | business-account-transfers.read | The transfer is requested and waiting for SCA (exempt for Transfers API). SEPA transfer input checks passed.. |
business-account-transfer.initiated | business-account-transfers.read | The transfer is SCA-authorized for execution. Funds are reserved. |
business-account-transfer.pending-review | business-account-transfers.read | The transfer is pending manual review. |
business-account-transfer.processed | business-account-transfers.read | The transfer is processed. |
business-account-transfer.failed | business-account-transfers.read | The transfer fails to be executed. |
business-account-transfer.blocked | business-account-transfers.read | The transfer is blocked. |
business-account-transfer.returned | business-account-transfers.read | Incoming transfer when a transfer is returned. |
payout.initiated | payouts.read | A payout has been initiated and is being processed. |
payout.completed | payouts.read | A payout has been successfully transferred to the merchant's bank account. |
payout.processing-at-bank | payouts.read | A payout has been submitted to the bank and is awaiting settlement. |
payout.canceled | payouts.read | A payout was canceled before it could be processed. |
payout.failed | payouts.read | A payout could not be completed due to an error. |
* | All of the above | All event types. You may specify * to add all events. Separate multiple event types with a comma. |
Beta Events
The following events are available in beta. To request access, contact support.
| Event type | Permissions Required | Description |
|---|---|---|
connect-balance-transfer.succeeded | balance-transfers.read | The balance transfer has successfully completed and is received by the destination. |
connect-balance-transfer.failed | balance-transfers.read | The balance transfer could not be completed. |
dispute.created | disputes.read | A new dispute has been opened on one of your payments. Review the dispute details and prepare your response before the deadline. |
dispute.resolved | disputes.read | A dispute has been updated — this may reflect a change in status, new information from the payment provider, or progress in the review process. |
dispute.updated | disputes.read | A dispute has been updated — this may reflect a change in status, new information from the payment provider, or progress in the review process. |
file.accepted | files.read | The file has been successfully received and processed. |
file.rejected | files.read | The file could not be accepted. This may be due to a format or size constraint. The statusReason field provides additional detail. Review the file and upload a new one if needed. |
file.failed | files.read | The file was uploaded successfully but could not be processed within the allowed time window, or an error occurred during processing. The statusReason field provides additional detail. You may retry by uploading a new file. |
unmatched-credit-transfer.received | unmatched-credit-transfers.read | A new unmatched credit transfer is created (matching failed). |
unmatched-credit-transfer.matched | unmatched-credit-transfers.read | The transfer is successfully matched and settled to payment(s). |
unmatched-credit-transfer.returned | unmatched-credit-transfers.read | The transfer has been returned to the sender. |
unmatched-credit-transfer.expired | unmatched-credit-transfers.read | The transfer expired without a resolution being applied. |
Limits
To ensure that your activities remain within the defined scope, Mollie enforces certain limitations when creating webhooks. We recommend utilizing a single webhook URL whenever possible to process multiple event types. If additional endpoints are necessary, ensure they are reserved for distinct use cases that actually require separation.
In order to maintain a clear distinction between Test and Live environments, the following limits have been put in place:
| Subscriptions | Test mode | Live mode |
|---|---|---|
| Active subscriptions per organization | 2 | Up to 3 |
| Same event type subscriptions | 2 | Up to 3 |
Retry schema
In response to Mollie calling your webhook, you only have to return the HTTP status 200 OK. Mollie then knows your system correctly processed the request.
If the URL is unreachable, takes too long to respond (>15s), or returns a non-success response, we will try to deliver the message again at a later point in time. We will keep trying to deliver the message for a total of 10 times in a 26-hour time span.
We use the following intervals between attempts while trying to call your webhook:
| Attempt | Time since previous attempt | Total time elapsed (HH:mm) |
|---|---|---|
| 1st | — | — |
| 2nd | 1 minute | 00:01 |
| 3rd | 2 minutes | 00:03 |
| 4th | 4 minutes | 00:07 |
| 5th | 8 minutes | 00:15 |
| 6th | 16 minutes | 00:31 |
| 7th | 29 minutes | 01:00 |
| 8th | 1 hour | 02:00 |
| 9th | 2 hours | 04:00 |
| 10th | 22 hours | 26:00 |
Blocked state
After a webhook has repeatedly failed to deliver events over the 24h period, it will be marked as blocked. At this point, we will cease all further delivery attempts and send out an email to notify you.
You can reactivate the blocked webhook via the Web App in Developers > Webhooks. It is important that you understand and resolve the underlying issue that caused the delivery failures: in your dashboard you can view all of the previous delivery attempts and the specific reasons for each failure, which can assist you in troubleshooting the issue.
Also, if the webhook is unresponsive for more than 30 minutes - we send you a notification.
Classic Mollie webhooks
Our new Webhook architecture builds upon the foundation of the previous payments webhooks system while introducing new features to enhance reliability, scalability, and ease of use. Though the core principles remain familiar, the updated structure includes subtle yet impactful changes to optimize how events are delivered and consumed.
Originally, our API only allowed configuration of webhooks by passing a webhookUrl to certain APIs, for example when creating a payment.
If you do set up webhooks in this way, our webhook system will only deliver the ID of the entity to the given webhook URL. You are expected to retrieve the full object yourself via the relevant API to understand what changed.
For example, if payment tr_d0b0E3EA3v moves to 'paid', the classic webhook looks like this:
POST /webhook HTTP/1.0
Host: webshop.example.org
Content-Type: application/x-www-form-urlencoded
Content-Length: 16
id=tr_d0b0E3EA3vTo understand what changed, you can now call the Get payment endpoint for payment tr_d0b0E3EA3v and compare it to the payment's status in your back-end.
We recommend using classic webhooks for payment-related updates (until we make these event types available for Next-gen webhooks) and Next-gen for any non-payment actions.
Shifting responsibilities
Learn about your responsibilities when using new webhooks:
- Verify the origin of the delivery and make sure that the data is coming from Mollie using the HMAC signature - a secure and reliable way to verify the authenticity and integrity of webhook requests. By using a shared secret key and hashing the payload, HMAC ensures that the data has not been tampered with and that the request originated from a trusted source.
- Periodically rotate signing secrets: to enhance security, it’s advisable to regularly rotate your secret keys or do so promptly if you suspect the secret key has been compromised or leaked.
- Ensure authenticity of the delivered content (e.g. by using Webhook Events API). If your system needs to support both webhook styles, differentiate between payments webhooks webcalls and new calls (new Webhooks send a new header
X-Mollie-Signaturewithin the request containing a signed event payload). - Secure the transmission of your data by properly configuring Transport Layer Security (TLS): obtain an SSL/TLS certificate, use a valid domain name, enforce a TLS version, and monitor and rotate certificates. It is also important to use secure URLs (HTTPS) for your webhooks - a version of HTTP that encrypts data exchanged between a user’s browser and a website to protect it from interception or tampering.
- Make sure your setup is compatible when using both new and old webhooks: although we recommend using separate URLs (one for each webhooks system), it's possible to keep both webhook systems running simultaneously, while sharing the same backend URL and accepting requests from both channels.
Troubleshooting
Updating a live signing secret
You can set up a new signing secret (i.e. rotate the secret) for an existing setup via the Mollie Dashboard or via the Webhooks API.
Once you provided the new secret, each event will contain two signature headers for the next 24 hours. For example:
POST /webhook HTTP/1.0
Host: webshop.example.org
X-Mollie-Signature: sha256=4a4c6f3ed4d15fee87ad44e07a7fa9b8
X-Mollie-Signature: sha256=a8994a2b90c785deeae0f7b0c6ae475fThis allows you to rotate the signing secret in your back-end. After 24 hours, the old secret will no longer be valid.
Redirecting webhook calls
Due to how HTTP works, if you have a standard redirect (301 Moved Permanently or 302 Found) in your webhook URL path, the request will be downgraded from a POST to a GET. You will therefore lose the request body.
To solve this, either remove the redirect from the webhook URL path, or use a 307 Temporary Redirect or 308 Permanent Redirect instead.
Error: invalid webhook location
Your webhook URL needs to be accessible from Mollie's point of view. This means that URL's like localhost will not be accepted. If you want to test webhooks locally, you should use a service like ngrok to make your local environment accessible from the internet.