Mollie Webhooks: Setup, Testing & Payment Status Guide (2026)
Mollie webhooks notify your application the moment a payment status changes. A customer completes a checkout, a payment fails, a refund is issued — instead of polling the Mollie API every few seconds, your server receives an HTTP POST with the payment ID, and you fetch the latest status. No wasted API calls. No stale data. Your order management system, subscription engine, or accounting integration stays in sync automatically.
This guide covers everything you need to set up, test, and debug Mollie webhooks in production. Whether you're building an e-commerce checkout, a SaaS billing flow, or a marketplace payout system, this is the reference that will save you hours of trial and error.
What Are Mollie Webhooks and How They Work
A Mollie webhook is an HTTP callback that Mollie sends to a URL you specify whenever a payment or related object changes status. Unlike most webhook providers that send the full event payload in the request body, Mollie takes a different approach: the webhook request body contains only the object ID (e.g., tr_7UhSN1zuXS). Your server then calls the Mollie API to fetch the current status.
This design is intentional. By requiring your server to fetch the latest state from the API, Mollie ensures you always work with the most current data — not a snapshot that could be outdated by the time you process it.
Here's the flow:
- A customer initiates a payment through your checkout
- The payment status changes (e.g., from
opentopaid) - Mollie sends an HTTP POST to your webhook URL with
id=tr_7UhSN1zuXSin the body - Your server receives the webhook and calls
GET /v2/payments/tr_7UhSN1zuXS - You read the current
statusfield and update your order accordingly
This pattern applies to all Mollie webhook events — payments, refunds, chargebacks, subscriptions, and orders.
Why Mollie Webhooks Matter for Your Business
Without webhooks, you'd need to poll the Mollie API repeatedly to check payment statuses. For a store processing 500 orders per day, that means thousands of unnecessary API calls. With Mollie webhooks, you process exactly the events that happen, when they happen:
- Instant order confirmation — Fulfill orders the second payment is confirmed, not minutes later
- Automated subscription management — React to failed recurring payments immediately and trigger dunning flows
- Real-time refund processing — Update your accounting system the moment a refund is issued
- Chargeback alerts — Get notified of disputes before they escalate
- Marketplace payouts — Track settlement status for multi-party payment flows
How to Configure Mollie Webhooks
You can set up Mollie webhooks in two ways: through the Mollie Dashboard for account-wide settings, or via the API for per-payment webhook URLs.
Method 1: Mollie Dashboard (Account-Level)
The dashboard approach sets a default webhook URL that applies to all payments created under your account.
- Log in to the Mollie Dashboard
- Navigate to Developers in the left sidebar
- Click Webhooks
- Enter your webhook endpoint URL (must be HTTPS in production)
- Click Save
This URL will be used for all payment status changes unless you override it at the API level.
Method 2: Mollie API (Per-Payment)
For more control, you can specify a webhook URL when creating each payment. This is the most common approach because it lets you route different payment types to different endpoints.
const { createMollieClient } = require('@mollie/api-client');
const mollieClient = createMollieClient({ apiKey: 'test_xxxxxxxxxxxxxxxxxxxxxxxx' });
const payment = await mollieClient.payments.create({
amount: {
currency: 'EUR',
value: '49.99'
},
description: 'Annual Pro Plan',
redirectUrl: 'https://yourapp.com/order/success',
webhookUrl: 'https://yourapp.com/api/webhooks/mollie',
metadata: {
orderId: 'ORD-2026-1847',
plan: 'pro-annual'
}
});
You can also set the webhook URL when creating orders, subscriptions, and refunds:
// Setting webhookUrl on an order
const order = await mollieClient.orders.create({
amount: { currency: 'EUR', value: '125.00' },
orderNumber: 'ORD-2026-1848',
webhookUrl: 'https://yourapp.com/api/webhooks/mollie-orders',
lines: [
{
name: 'Wireless Keyboard',
quantity: 1,
unitPrice: { currency: 'EUR', value: '75.00' },
totalAmount: { currency: 'EUR', value: '75.00' },
vatRate: '21.00',
vatAmount: { currency: 'EUR', value: '13.02' }
},
{
name: 'USB-C Hub',
quantity: 1,
unitPrice: { currency: 'EUR', value: '50.00' },
totalAmount: { currency: 'EUR', value: '50.00' },
vatRate: '21.00',
vatAmount: { currency: 'EUR', value: '8.68' }
}
],
billingAddress: {
givenName: 'Jan',
familyName: 'de Vries',
email: 'jan@example.com',
streetAndNumber: 'Keizersgracht 313',
postalCode: '1016 EE',
city: 'Amsterdam',
country: 'NL'
},
redirectUrl: 'https://yourapp.com/order/success'
});
The per-payment webhook URL always takes priority over the dashboard default.
Mollie Webhook Events
Mollie webhooks fire whenever the status of a payment, refund, chargeback, subscription, or order changes. Here are the key status transitions you need to handle:
Payment Status Changes
| Status | Meaning | Typical Action |
|---|---|---|
open | Payment created, customer hasn't completed it yet | Show "awaiting payment" in your UI |
pending | Payment initiated but not yet confirmed (bank transfers, SOFORT) | Show "processing" status |
authorized | Payment authorized but not yet captured (credit cards with manual capture) | Capture the payment or cancel |
paid | Payment successfully completed | Fulfill the order, send confirmation email |
failed | Payment failed (insufficient funds, 3DS failure, etc.) | Show error, offer retry |
canceled | Customer or merchant canceled the payment | Release reserved inventory |
expired | Payment expired before completion | Clean up the order, notify customer |
Refund Status Changes
| Status | Meaning | Typical Action |
|---|---|---|
queued | Refund is queued for processing | Update order status to "refund pending" |
pending | Refund is being processed by the bank | No action needed, wait for completion |
refunded | Refund successfully completed | Update accounting, notify customer |
failed | Refund failed | Investigate and retry or process manually |
Other Webhook Triggers
- Chargebacks — When a customer disputes a payment with their bank
- Subscriptions — When a recurring payment succeeds, fails, or is canceled
- Settlements — When funds are settled to your bank account (Connect/marketplace)
- Orders — When order status changes (authorized, paid, shipping, completed, etc.)
Sample Mollie Webhook Payload and Handling
When a Mollie webhook fires, the request is deliberately simple. Here's exactly what your server receives:
Incoming Webhook Request
POST /api/webhooks/mollie HTTP/1.1
Host: yourapp.com
Content-Type: application/x-www-form-urlencoded
User-Agent: Mollie HTTP
id=tr_7UhSN1zuXS
That's it. The body contains only id=tr_7UhSN1zuXS as a form-encoded parameter. No JSON payload. No event type. No status. You must call the Mollie API to get the details.
Fetching the Payment Status
Here's a complete webhook handler in Node.js/Express:
const express = require('express');
const { createMollieClient } = require('@mollie/api-client');
const app = express();
app.use(express.urlencoded({ extended: true }));
const mollieClient = createMollieClient({
apiKey: process.env.MOLLIE_API_KEY
});
app.post('/api/webhooks/mollie', async (req, res) => {
// Always respond 200 immediately — Mollie retries on non-2xx
res.status(200).send('OK');
const paymentId = req.body.id;
if (!paymentId) return;
try {
const payment = await mollieClient.payments.get(paymentId);
console.log(`Payment ${paymentId} status: ${payment.status}`);
switch (payment.status) {
case 'paid':
await fulfillOrder(payment.metadata.orderId);
await sendConfirmationEmail(payment.metadata.orderId);
break;
case 'failed':
await markOrderFailed(payment.metadata.orderId);
await sendPaymentFailedEmail(payment.metadata.orderId);
break;
case 'canceled':
await cancelOrder(payment.metadata.orderId);
await releaseInventory(payment.metadata.orderId);
break;
case 'expired':
await expireOrder(payment.metadata.orderId);
break;
case 'refunded':
await processRefund(payment.metadata.orderId, payment.amountRefunded);
break;
default:
console.log(`Unhandled payment status: ${payment.status}`);
}
} catch (error) {
console.error(`Error processing webhook for ${paymentId}:`, error);
}
});
Full Payment Object from the API
When you call mollieClient.payments.get(paymentId), you receive a comprehensive payment object. Here's a realistic example:
{
"resource": "payment",
"id": "tr_7UhSN1zuXS",
"mode": "live",
"createdAt": "2026-04-11T10:23:41+00:00",
"amount": {
"value": "49.99",
"currency": "EUR"
},
"description": "Annual Pro Plan",
"method": "ideal",
"metadata": {
"orderId": "ORD-2026-1847",
"plan": "pro-annual"
},
"status": "paid",
"paidAt": "2026-04-11T10:24:15+00:00",
"amountRefunded": {
"value": "0.00",
"currency": "EUR"
},
"amountRemaining": {
"value": "49.99",
"currency": "EUR"
},
"locale": "nl_NL",
"countryCode": "NL",
"profileId": "pfl_QkEhN94Ba",
"settlementAmount": {
"value": "49.70",
"currency": "EUR"
},
"details": {
"consumerName": "Jan de Vries",
"consumerAccount": "NL53INGB0654422370",
"consumerBic": "INGBNL2A"
},
"redirectUrl": "https://yourapp.com/order/success",
"webhookUrl": "https://yourapp.com/api/webhooks/mollie",
"_links": {
"self": {
"href": "https://api.mollie.com/v2/payments/tr_7UhSN1zuXS",
"type": "application/hal+json"
},
"checkout": {
"href": "https://www.mollie.com/checkout/ideal/7UhSN1zuXS",
"type": "text/html"
}
}
}
Key fields to note:
statusis the field you'll use most — it drives your business logicmetadatacontains the custom data you passed when creating the payment (use it to link back to your order)detailscontains payment-method-specific information (bank name, card details, etc.)settlementAmounttells you the actual amount after Mollie's fees
How to Test Mollie Webhooks with HookRay
Testing Mollie webhooks during development is challenging because Mollie requires a publicly accessible HTTPS URL. Your local localhost:3000 won't work. Even Mollie's test mode requires a reachable endpoint. This is where HookRay makes the workflow seamless.
Step 1: Get a HookRay Endpoint
- Go to hookray.com/app
- A unique webhook URL is generated instantly — no signup required
- Copy the URL (it looks like
https://hookray.com/api/wh/your-unique-id)
Step 2: Use the HookRay URL with Mollie
Pass your HookRay URL as the webhookUrl when creating test payments:
const payment = await mollieClient.payments.create({
amount: { currency: 'EUR', value: '10.00' },
description: 'Test Payment',
redirectUrl: 'https://yourapp.com/order/success',
webhookUrl: 'https://hookray.com/api/wh/your-unique-id',
metadata: { orderId: 'TEST-001' }
});
Or paste the HookRay URL into the Mollie Dashboard webhook settings for account-wide testing.
Step 3: Complete a Test Payment
In Mollie's test mode, use the test payment flow:
- Create a payment using the Mollie API with your test API key
- Redirect to the checkout URL returned by Mollie
- Select a payment status (paid, failed, canceled, expired) in the test checkout screen
- Mollie sends a webhook to your HookRay URL
Step 4: Inspect the Webhook in Real Time
Switch to your HookRay dashboard. You'll see the incoming Mollie webhook request instantly:
- View the form-encoded body (
id=tr_xxxxx) - Check HTTP headers sent by Mollie (Content-Type, User-Agent)
- Compare multiple webhook deliveries side by side
- Replay the webhook to your local server once your handler is ready
This lets you see exactly what Mollie sends before writing a single line of handler code. You can test every payment status transition — paid, failed, canceled, expired — and verify your endpoint receives them all correctly.
Step 5: Build with Confidence
With HookRay capturing real Mollie webhook data, your development loop becomes:
- Create a test payment via the Mollie API
- Complete it with the desired status in the Mollie test checkout
- See the webhook arrive in HookRay instantly
- Build your handler based on the actual request format
- Replay captured webhooks to your local endpoint for testing
For more Mollie-specific webhook patterns, visit our Mollie webhook reference.
Verifying Mollie Webhook Authenticity
Unlike providers that sign webhook payloads with HMAC, Mollie's webhook verification model is simpler because of the callback-based design. Since the webhook body only contains an ID, and your server fetches the actual data from the Mollie API using your secret API key, authenticity is inherently verified — only someone with your API key could get the payment data.
However, you should still protect your webhook endpoint from abuse:
1. Validate the Payment ID Format
Mollie payment IDs follow a consistent format (tr_ prefix followed by alphanumeric characters). Reject anything that doesn't match:
app.post('/api/webhooks/mollie', async (req, res) => {
const paymentId = req.body.id;
// Validate format before making any API calls
if (!paymentId || !/^tr_[a-zA-Z0-9]+$/.test(paymentId)) {
return res.status(200).send('OK'); // Still return 200 to avoid retries
}
// Proceed with API call...
});
2. Verify via API Response
If someone sends a fake payment ID, the Mollie API will return a 404. Your handler should catch this gracefully:
try {
const payment = await mollieClient.payments.get(paymentId);
// Valid payment — process it
} catch (error) {
if (error.status === 404) {
console.warn(`Unknown payment ID received: ${paymentId}`);
return; // Ignore — likely a spoofed request
}
throw error; // Re-throw real errors
}
3. Verify Metadata Matches Your Records
Always cross-reference the payment metadata with your own database:
const payment = await mollieClient.payments.get(paymentId);
const order = await db.orders.findByOrderId(payment.metadata.orderId);
if (!order) {
console.warn(`Payment ${paymentId} has unknown orderId: ${payment.metadata.orderId}`);
return;
}
4. Use a Secret Path or Token
Add a hard-to-guess segment to your webhook URL as an extra layer:
https://yourapp.com/api/webhooks/mollie?token=a1b2c3d4e5f6
Validate the token in your handler before processing.
Common Mollie Webhook Issues and Debugging
Even with a straightforward setup, Mollie webhooks can behave unexpectedly. Here are the issues developers encounter most often.
Webhook Not Being Called
Symptoms: You create a payment, complete it in the test checkout, but your endpoint never receives a request.
Causes and fixes:
-
URL not publicly accessible — Mollie cannot reach
localhost,127.0.0.1, or internal network addresses. Use HookRay for instant public URLs during development. -
Missing HTTPS in live mode — Live mode requires HTTPS. Test mode allows HTTP but HTTPS is still recommended.
-
Webhook URL not set — Verify the
webhookUrlparameter is included in your payment creation call. Check for typos in the URL. -
Firewall or WAF blocking — Your server's firewall or web application firewall might block Mollie's requests. Allowlist the Mollie User-Agent (
Mollie HTTP) or Mollie's IP ranges. -
DNS not resolved — If you recently set up a domain, DNS propagation might not be complete. Verify your domain resolves correctly.
Receiving Webhooks But Failing to Process
Symptoms: Mollie sends the webhook, but your handler crashes or produces errors.
Causes and fixes:
-
Parsing as JSON instead of form-encoded — Mollie sends
application/x-www-form-urlencoded, not JSON. In Express, useexpress.urlencoded()middleware, notexpress.json(). -
Wrong API key — If you created the payment with a test key but your webhook handler uses a live key (or vice versa), the API call to fetch payment details will fail with a 404.
-
Async/await errors silently swallowed — If you respond with 200 before processing and don't catch errors in the async processing, failures go unnoticed. Always wrap async processing in try/catch with proper logging.
Duplicate Webhook Deliveries
Symptoms: Your handler processes the same payment multiple times.
Causes and fixes:
-
Mollie retries on non-2xx — If your endpoint doesn't return a 2xx status code quickly enough (within ~15 seconds), Mollie retries. Always return 200 immediately, then process asynchronously.
-
Multiple status changes — A single payment can trigger multiple webhooks as it transitions through statuses (open -> pending -> paid). This is normal. Make your handler idempotent.
// Idempotent handler example
const payment = await mollieClient.payments.get(paymentId);
const order = await db.orders.findByOrderId(payment.metadata.orderId);
// Only process if the order status hasn't already been updated
if (order.status !== payment.status) {
await db.orders.updateStatus(order.id, payment.status);
// Trigger side effects...
}
Testing Without a Public URL
Symptoms: You want to test webhooks locally without deploying.
Solution: Use HookRay to capture Mollie webhooks in real time, inspect the payloads, and replay them to your local development server. This is faster and more reliable than setting up tunneling tools.
Best Practices for Production Mollie Webhooks
-
Always respond 200 first — Return
200 OKimmediately, then process asynchronously. Mollie interprets slow responses as failures and retries. -
Build idempotent handlers — The same webhook may arrive multiple times. Use the payment ID and status as a compound key to prevent duplicate processing.
-
Use metadata — Pass your internal order ID, customer ID, and plan details in the
metadatafield when creating payments. This links Mollie payments to your domain objects without extra database lookups. -
Log every webhook — Capture the payment ID, fetched status, and processing result for every webhook. This audit trail is invaluable for debugging payment issues.
-
Handle all statuses — Don't just handle
paid. Implement handlers forfailed,canceled,expired, andrefundedto keep your system consistent. -
Test every status transition — Use Mollie's test mode with HookRay to verify your handler works for every possible status. The test checkout lets you simulate paid, failed, canceled, and expired states.
-
Monitor webhook processing — Track success rates, processing times, and error rates. Set alerts for when webhook processing starts failing.
Start Testing Your Mollie Webhooks Today
Setting up Mollie webhooks is straightforward, but building reliable payment integrations requires seeing real webhook requests, testing every status transition, and debugging delivery issues. HookRay gives you instant webhook URLs with real-time inspection, search, and replay — everything you need to build and test Mollie integrations with confidence.
Get started in 5 seconds:
- Go to hookray.com/app
- Copy your unique webhook URL
- Pass it as
webhookUrlin your Mollie payment creation call - See webhooks arrive in real time
No signup. No credit card. Just fast, reliable webhook testing.
Building a Mollie payment integration? Try HookRay free — inspect, debug, and replay your Mollie webhooks in real time.
Ready to test your webhooks?
Get a free webhook URL in 5 seconds. No signup required.
Start Testing — Free