Skip to content

建立 2026-09-14 更新 2026-09-14

Webhook 履約

Webhook 是 Stripe 主動 POST 到你伺服器的事件。顧客可能付完就關分頁,成功頁不是履約依據。官方文件:Receive Stripe events

為什麼一定要驗證簽章

/webhook 若公開在網路上,攻擊者可以自己 POST 一份 checkout.session.completed。驗證 Stripe-Signature 才能確定請求來自 Stripe,且內容沒被改過。

驗證必須使用原始 request body(bytes)。先 request.get_json() 再驗證,簽章會失敗。

Flask 範例

import os
import stripe
from flask import Flask, request

app = Flask(__name__)
stripe.api_key = os.environ["STRIPE_SECRET_KEY"]
WEBHOOK_SECRET = os.environ["STRIPE_WEBHOOK_SECRET"]

processed = set()  # 示範用;正式請存資料庫

@app.post("/webhook")
def webhook():
    payload = request.get_data()
    sig_header = request.headers.get("Stripe-Signature")
    try:
        event = stripe.Webhook.construct_event(
            payload, sig_header, WEBHOOK_SECRET
        )
    except ValueError:
        return "Invalid payload", 400
    except stripe.error.SignatureVerificationError:
        return "Invalid signature", 400

    if event["id"] in processed:
        return "", 200
    processed.add(event["id"])

    if event["type"] == "checkout.session.completed":
        session = event["data"]["object"]
        if session.get("payment_status") == "paid":
            fulfill(session)
    elif event["type"] == "payment_intent.succeeded":
        intent = event["data"]["object"]
        fulfill_intent(intent)

    return "", 200

def fulfill(session):
    sku = (session.get("metadata") or {}).get("sku")
    # 標記訂單已付款、寄信、開通權限
    print("fulfill", session["id"], sku)

def fulfill_intent(intent):
    print("paid", intent["id"], intent["amount"])

處理成功後盡快回 200。邏輯太慢時,先記 event id 再背景處理,否則 Stripe 會重試。

要聽哪些事件

你用的整合 主要事件
Checkout 一次付清 checkout.session.completed(確認 payment_status=paid
PaymentIntent payment_intent.succeeded
訂閱 customer.subscription.created / updated / deletedinvoice.paidinvoice.payment_failed

一次付清建議兩個都做:Checkout 完成、以及對應的 PaymentIntent 成功。以你的訂單狀態機做冪等,重複呼叫只更新一次。

本機測試

stripe listen --forward-to localhost:4242/webhook

把印出的 whsec_... 設成 STRIPE_WEBHOOK_SECRET。另開終端:

stripe trigger payment_intent.succeeded

CLI 密鑰只給本機轉送用。上線後在 Dashboard → Webhooks 新增 https://你的網域/webhook,會得到另一把 whsec_,放到正式環境變數。

常見錯誤

  • 用 JSON parser 吃掉 body 再驗證 → 簽章失敗
  • 本機 whsec 拿到正式機、或 Dashboard 密鑰拿到 CLI → 同樣失敗
  • 收到事件就出貨、沒看 payment_status → 未付款 Session 也可能有 completed 以外的狀態組合,一次付清請確認 paid
  • 沒做 event id 去重 → 顧客收到兩次商品