Mollie 中文文档 下载 App

Mollie Webhooks 事件回调配置 - 支付事件实时推送

Webhook 是什么

Webhook 是 Mollie 主动向你的服务器发送的 HTTP POST 请求。每当 Mollie 这边有事件发生(比如"某笔支付成功了"、"某个订阅被取消了"、"收到一笔拒付"),Mollie 的服务器就把这个事件打包成一个 HTTP POST 发到你配置的 URL。

这和你"主动去 Mollie 服务器查询"(Polling)的区别是:你不用轮询,事件来了 Mollie 自己推给你——实时、高效、不会漏掉。

为什么不能用 Redirect URL 代替 Webhook

很多新手以为"客户付款后 Mollie 会重定向回我的网站,我在那里处理就行"——这不够。

场景Redirect URLWebhook
客户付款成功后正常跳转✅ 能收到✅ 能收到
客户付款成功后关闭浏览器❌ 收不到✅ 能收到
3DS 验证后跳转失败❌ 收不到✅ 能收到
订阅自动扣费成功不触发重定向✅ 能收到
拒付 Chargeback不触发重定向✅ 能收到
退款完成不触发重定向✅ 能收到

结论:Redirect URL 是给客户看的,Webhook 才是给你的服务器用来确认支付状态的唯一可靠信号。

配置步骤

Dashboard 里添加 Webhook

Dashboard → Developers → Webhooks → [Create Webhook]
  │
  ├── Name: "My App Production Webhook"(给你自己看的名字)
  ├── URL: "https://myapp.com/api/mollie/webhook"(你的服务器地址,必须 HTTPS)
  │
  └── Payload 风格选择:
        ├── "ID only"(默认,最简洁)→ POST body 只有 payment id
        └── "Full payload"(详细)→ POST body 包含完整的 payment 对象

配置要求

要求原因
必须 HTTPSMollie 不会向 HTTP URL 推送
必须公网可访问localhost 不行,需要真实域名
必须返回 HTTP 200且在 10 秒内返回
幂等处理同一个 Webhook 可能重复推送(网络抖动时 Mollie 重试)

你的 Webhook 处理器逻辑

收到 Webhook 请求后,你的服务器应该:

1. 验证请求来源(可选但推荐)
   │
2. 从 body 里提取 payment id / order id / subscription id 等
   │
3. 立即返回 HTTP 200(不要等后面的处理完成!)
   │
4. 用提取的 id 调用 Mollie API 获取最新状态
   │
5. 根据状态更新你的本地数据库
   │
   └── payment.paid → 订单状态改为"已付款"
       payment.failed → 标记付款失败
       subscription.canceled → 标记订阅取消
       chargeback.received → 记录拒付

为什么要"立即返回 200"

Mollie 有一个10 秒超时:如果 10 秒内没收到你的 200 响应,Mollie 会认为推送失败,稍后自动重试(最多 5 次)。如果你的处理逻辑很耗时(比如要调用其他外部 API),应该:

Python 示例(Flask)

import os
from flask import Flask, request

app = Flask(__name__)

@app.route("/api/mollie/webhook", methods=["POST"])
def mollie_webhook():
    # 1. 提取 id
    data = request.get_json()  # 或者 request.form.get("id")
    payment_id = data.get("id")
    
    if not payment_id:
        return "", 400
    
    # 2. 立即返回 200!不要等下面处理完
    # Flask 默认在函数返回时发送响应,但异步处理更好
    
    # 3. 用 payment_id 查询最新状态(异步处理)
    import threading
    def handle_payment(pid):
        # 调用 Mollie API 获取 payment 状态
        # 根据状态更新你的订单系统
        pass
    
    threading.Thread(target=handle_payment, args=(payment_id,)).start()
    
    return "", 200

if __name__ == "__main__":
    app.run()

幂等性处理

同一个 Webhook 事件可能被推送多次。你的处理逻辑必须是幂等的——多次收到同一个事件,最终结果和收到一次一样。

为什么会重复推送

怎么做幂等

-- 在订单表里加一个 webhook_processed 字段
UPDATE orders 
SET status = 'paid', webhook_processed = true 
WHERE id = ? AND webhook_processed IS NULL;

-- 用 affected_rows 判断
-- 如果返回 0,说明这条 Webhook 已经处理过了,跳过
-- 如果返回 1,正常处理

常见 Webhook 事件类型

事件触发对应对象
payment.createdPayment 创建时Payment
payment.paid支付成功时Payment
payment.failed支付失败时Payment
payment.expired支付超时未完成时Payment
payment.canceled支付被取消时Payment
payment.refunded退款完成时Payment
subscription.created订阅创建时Subscription
subscription.canceled订阅取消时Subscription
subscription.activated订阅激活时Subscription
chargeback.received收到拒付时Payment
order.createdOrder 创建时Order
order.paidOrder 支付成功时Order
order.refundedOrder 退款时Order

测试 Webhook

方法一:Dashboard 手动触发

Dashboard → Developers → Webhooks → 选你的 Webhook → "Send test event"。会向你的 URL 推送一个模拟事件。

方法二:用测试环境

在测试环境里完成一笔"付款"(Mock Payment),Mollie 会像正式环境一样推送 Webhook 到你的测试 Webhook URL。

方法三:内网穿透

本地开发时可以用 ngrok / localtunnel 把 localhost 暴露到公网,然后把穿透后的 URL 填到 Dashboard 里。这样 Mollie 就能推到你本地服务器上。

安全:验证 Webhook 来源

Mollie 在每个 Webhook 请求里放了一个 Mollie-Signature HTTP 头,内容是 HMAC-SHA256 签名。你可以用 API Key 来验证这个签名,确保请求来自 Mollie 而不是伪造的。

import hmac
import hashlib

api_key = "live_xxxxxxxxxxxxxxxx"
signature = request.headers.get("Mollie-Signature")
raw_body = request.get_data()

# Mollie 用 API Key 作为 HMAC 密钥
expected = hmac.new(api_key.encode(), raw_body, hashlib.sha256).hexdigest()

if not hmac.compare_digest(signature, expected):
    # 签名不匹配,可能是伪造的请求
    return "", 400

继续阅读:SDK 与预置插件 →