Next-gen Webhooks (beta)

🚧

This feature is in beta.
We are gradually adding support for more event types to the new architecture.

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.

Overview

To start receiving webhooks, you need to:

  1. Create a public URL in your back-end that will be handling the HTTP requests.
  2. Set up webhook signature verification.
  3. 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 HTTPS

There are no security requirements for test mode. However, in order to receive live mode 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": {
    "payment-link": {
      "resource": "payment-link",
      "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 official Mollie libraries to verify the signature. For example:

<?php
$mollie = new \Mollie\Api\MollieApiClient();

$signingSecret = "foobar";

$request = \GuzzleHttp\Psr7\ServerRequest::fromGlobals();
$requestBody = (string)$request->getBody();
$signature = $request->getHeader("X-Mollie-Signature");

try {
    // Let the Mollie SDK verify the signature and parse the payload into an object.
    $event = $mollie->parseWebhookEvent(
        $requestBody,
        $signature,
        $signingSecret,
    );
} catch (InvalidSignature) {
    return new \GuzzleHttp\Psr7\Response(400);
}

if ($event === \Mollie\Api\WebhookEventType::PAYMENT_PAID) {
    // Process the 'paid' event.
} else {
    // Unexpected event type.
    return new \GuzzleHttp\Psr7\Response(400);
}

// Return success status.
return new \GuzzleHttp\Psr7\Response(200);

Manual signature verification

We do not recommend performing manual signature verification, but you can create a custom solution as follows:

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:

  1. Remove the sha256= prefix from the signature header, leaving you with the raw signature value.
  2. Calculate a SHA-256-based HMAC using the unaltered POST body of the webhook request as the 'message', and the signing secret you provided to us.
  3. 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:

Event typeDescription
payment-link.paidA payment link moved to the paid status.
profile.createdThe profile is not verified yet and can only be used to create test payments.
profile.verifiedThe profile is verified and can be used to create test payments and live payments.
profile.blockedThe profile is blocked and can no longer be used or changed.
profile.deletedThe profile is deleted.

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:

SubscriptionsTest modeLive mode
Active subscriptions per organization1Up to 3
Same event type subscriptions1Up to 2

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:

AttemptTime since previous attemptTotal time elapsed (HH:mm)
1st
2nd1 minute00:01
3rd2 minutes00:03
4th4 minutes00:07
5th8 minutes00:15
6th16 minutes00:31
7th29 minutes01:00
8th1 hour02:00
9th2 hours04:00
10th22 hours26:00

Classic Mollie webhooks

🚧

We no longer recommend implementing classic Mollie Webhooks, given their limited functionality. Please gradually migrate available event types to the new Webhooks instead.

We are actively working on adding support for more event types.

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.

These parameters are considered deprecated.

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_d0b0E3EA3v

To 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.

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 an archive of events sent to you via webhooks and enable retrieving detailed information about triggered events.
  • Flexible payload delivery: choose between a full payload (snapshot) that gives detailed context data including full resource payload and metadata (recommended) or a simple payload that returns basic information, such as identifiers and event type that triggered it.

    //Full payload example
    
    {
      "resource": "event",
      "id": "event_GvJ8WHrp5isUdRub9CJyH",
      "type": "payment-link.paid",
      "entityId": "pl_qng5gbbv8NAZ5gpM5ZYgx",
      "createdAt": "2024-12-16T15:57:04.0Z",
      "_embedded": {
        "payment-link": {
          "resource": "payment-link",
          "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.

Shifting responsibilities

Learn about your responsibilities when using 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-Signature within 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=a8994a2b90c785deeae0f7b0c6ae475f

This 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.