---
title: "Receive Mollie Webhooks Locally | WebhookRelay"
meta:
  "og:description": "Test Mollie webhooks locally on localhost without deploying. Mollie posts only the payment id, so inspect the real request, forward to your handler, and fetch status from the API."
  "og:title": "Receive Mollie Webhooks Locally"
  description: "Test Mollie webhooks locally on localhost without deploying. Mollie posts only the payment id, so inspect the real request, forward to your handler, and fetch status from the API."
---

![Stripes](https://webhookrelay.com/blog/receive-mollie-webhooks-locally/images/stripes.svg)

# **Receive Mollie Webhooks Locally**

Test Mollie webhooks locally on localhost without deploying. Mollie posts only the payment id, so inspect the real request, forward to your handler, and fetch status from the API.

![Receive Mollie Webhooks Locally (Test Mollie Webhooks on localhost)](https://webhookrelay.com/blog/receive-mollie-webhooks-locally/images/blog/heroes/localhost.jpg)

You are integrating Mollie — taking payments across iDEAL, cards, and SEPA — and you need to see your handler react when a payment moves to `paid`, `failed`, or `expired`. The problem is immediate: Mollie will only POST to a **public URL**, and your handler is running on `localhost:3000`. Mollie has no way to reach it.

The usual workarounds are slow. Deploying to a staging server for every code change kills your iteration speed. And Mollie has a twist that catches people off guard: the webhook does **not** carry the payment details at all. So copying a "sample payload" from somewhere is doubly misleading. What you want is to **test Mollie webhooks locally** — real notifications, hitting your local handler, on a URL that does not change every time you restart.

This guide shows how to do exactly that.

## [How Mollie's webhook is different (and why it matters)](#how-mollies-webhook-is-different-and-why-it-matters)

Most providers POST the full event to your URL. Mollie does not. Its classic webhook is a deliberately **thin notification**: when a payment's status changes, Mollie sends an HTTP POST whose body is a single form-encoded parameter — `id=tr_xxxxxxxx` — and nothing else. No JSON object, no status field, no signature header.

That is by design. The contract is: _"object `tr_xxxxxxxx` changed — go ask the API what it looks like now."_ Your handler takes the `id`, calls the Mollie API with your own API key, reads the authoritative `status`, and only then fulfils the order. The security benefit is real: because the status comes from an authenticated API call rather than the POST body, a forged request to your webhook can never mark an order as paid. This is also why classic Mollie webhooks ship **no HMAC signature** — there is nothing in the body worth signing.

Knowing this up front saves you from writing a handler that trusts a payload that does not exist.

## [Why testing Mollie webhooks on localhost is tricky](#why-testing-mollie-webhooks-on-localhost-is-tricky)

Mollie sits on the public internet; your dev machine usually does not. It is behind a router, a corporate firewall, or both, with no public IP and no inbound ports open. So you need something in the middle: a public endpoint Mollie can hit, that relays each request down to your laptop without you opening a single firewall port.

That is what [Webhook Relay](https://my.webhookrelay.com/register) does — and unlike a random tunnel URL, the endpoint is **stable**, so the `webhookUrl` you set on your payments keeps working across restarts.

## [Step 1: Inspect the real request with Webhook Bin](#step-1-inspect-the-real-request-with-webhook-bin)

Before you write any handler code, confirm for yourself exactly what Mollie sends. Open the free [Webhook Bin](https://webhookrelay.com/blog/receive-mollie-webhooks-locally/webhook-bin/) — no signup — and you get an instant public URL.

1. Copy the Webhook Bin URL.
2. Create a test payment via the Mollie API and pass the Webhook Bin URL as the `webhookUrl` field. (You can also set a default webhook on a profile in the Mollie Dashboard.)
3. Complete or cancel the payment in Mollie's test checkout.

The notification lands in Webhook Bin right away. Inspect it and you will see exactly the shape described above: a `Content-Type: application/x-www-form-urlencoded` body containing only `id=tr_...`, and standard HTTP headers with **no** signature header. Seeing the real, tiny request makes the "fetch from the API" flow click. For more on this approach, see [How to test webhooks](https://webhookrelay.com/blog/receive-mollie-webhooks-locally/blog/how-to-test-webhooks/) and [What is a webhook](https://webhookrelay.com/blog/receive-mollie-webhooks-locally/blog/what-is-webhook/).

## [Step 2: Forward the notifications to localhost with the relay agent](#step-2-forward-the-notifications-to-localhost-with-the-relay-agent)

Now route those same notifications into your local handler. [Sign up for Webhook Relay](https://my.webhookrelay.com/register), install the `relay` agent (CLI or Docker), and create a bucket — say `mollie`. The bucket gives you a stable public input endpoint.

Start forwarding to your local server:

```
relay forward --bucket mollie http://localhost:3000/webhooks/mollie
```

The agent opens an **outbound** connection to Webhook Relay and streams every incoming request down to `http://localhost:3000/webhooks/mollie`. Because the connection is outbound, there are **no firewall ports to open and no public IP needed** — this works from your laptop, behind a corporate proxy, or inside a Kubernetes cluster. Running in Docker? The same command works in the official `webhookrelay/webhookrelayd` image. Full details are in the [localhost forwarding docs](https://webhookrelay.com/blog/receive-mollie-webhooks-locally/docs/webhooks/internal/localhost/).

Now set the Webhook Relay endpoint as your `webhookUrl` (or in your dashboard profile), create a test payment, and watch the `id=tr_...` notification arrive on `localhost`.

## [What your local handler should do](#what-your-local-handler-should-do)

Because the POST only carries an id, your handler's job is a fixed three-step dance:

1. **Read the `id`** from the form-encoded body (for a payment it is `tr_...`; subscriptions and orders use other prefixes).
2. **Fetch from the Mollie API** — `GET https://api.mollie.com/v2/payments/{id}` with your API key — to read the authoritative current `status` (`open`, `paid`, `failed`, `canceled`, `expired`).
3. **Act on the real status** — fulfil, retry, or ignore — and always return `200 OK` quickly so Mollie does not retry unnecessarily.

A few Mollie quirks worth knowing:

- **Retries:** if your endpoint does not return a `2xx`, Mollie retries the notification on a schedule. Make your handler idempotent — the same `id` may arrive several times.
- **No status in the body:** never branch on anything in the POST body except the `id`. The status only exists in the API response.
- **Test vs live keys:** fetch the payment with the API key from the **same** mode (test/live) the payment was created in, or the lookup will 404.

## [Verifying a Mollie webhook](#verifying-a-mollie-webhook)

Classic Mollie webhooks have **no signature** to check — the verification _is_ the API fetch. Looking up the `id` with your secret API key is what proves the notification is genuine, because only a real payment in your account will resolve.

That said, the moment you start adding HMAC-signed webhooks from other providers (Stripe, GitHub, Razorpay), you will want a repeatable way to check signatures. The free [HMAC signature verifier](https://webhookrelay.com/blog/receive-mollie-webhooks-locally/hmac-verification/) and the guide on how to [verify a webhook signature](https://webhookrelay.com/blog/receive-mollie-webhooks-locally/blog/verify-webhook-signature/) cover that flow — handy when your stack mixes Mollie with a provider that _does_ sign its payloads.

## [Replay and iterate](#replay-and-iterate)

This is where local development gets fast:

- **Replay from Webhook Relay** — past notifications are stored on your bucket, so you can resend the same `id=tr_...` POST without creating a brand-new payment in Mollie. Your handler re-runs the full fetch-and-fulfil flow against a real payment id.
- **Iterate on your handler** by editing code and replaying the same notification until the API fetch, status handling, and order logic all behave. No redeploys to test the `expired` path.

Because the Webhook Relay endpoint is stable, you can stop and restart the agent, reboot your machine, or come back next week — the `webhookUrl` on your payments never needs to change. If you are also wiring up another payment provider, the same flow works for [Stripe webhooks on localhost](https://webhookrelay.com/blog/receive-mollie-webhooks-locally/blog/receiving-stripe-webhooks-localhost/).

## [Get started](#get-started)

1. Inspect the real notification in the free [Webhook Bin](https://webhookrelay.com/blog/receive-mollie-webhooks-locally/webhook-bin/) — no signup needed.
2. [Create a Webhook Relay account](https://my.webhookrelay.com/register), install the agent, and run `relay forward --bucket mollie http://localhost:3000/webhooks/mollie`.
3. Point your Mollie `webhookUrl` at the stable endpoint, create a test payment, and watch the `id` notification hit `localhost`.

You will be testing real Mollie notifications against your local handler in a few minutes — no deploys, no open firewall ports, and a URL you configure exactly once.