コンテンツにスキップ
アカウント作成/サインイン
Stripe ドキュメントのロゴ
/
AI に質問する
アカウントを作成サインイン
導入方法
決済管理
売上管理
プラットフォームとマーケットプレイス
資金管理
開発者向けリソース
API & SDKヘルプ
概要
バージョン管理
変更ログ
API バージョンのアップグレード
SDK バージョンをアップグレードする
Essentials
SDK
API
テスト
Stripe CLI
サンプルプロジェクト
ツール
Stripe ダッシュボード
ワークベンチ
開発者ダッシュボード
Stripe Shell
Visual Studio Code をご利用の場合
機能
ワークフロー
イベントの送信先
    イベントとの連携
    Amazon EventBridge
    Webhook エンドポイント
      Webhook ビルダー
      決済イベントの処理
      Webhook のバージョン管理
      Migrate to thin events
      Webhook の署名確認エラーを解決
      未配信の Webhook イベントを処理
      回復不能な Webhook イベントを処理する
Stripe 健全性アラートファイルのアップロード
AI ソリューション
エージェントツールキット
モデルコンテキストプロトコルエージェントを使用した AI SaaS 請求ワークフローの構築
セキュリティとプライバシー
セキュリティ
Stripebot ウェブクローラー
プライバシー
Stripe を拡張する
Stripe Appsを構築する
Stripe アプリを使用する
パートナー
Partner Ecosystem
パートナー認定
アメリカ
日本語
ホーム開発者向けリソースEvent destinationsWebhook endpoint

メモ

このページはまだ日本語ではご利用いただけません。より多くの言語で文書が閲覧できるように現在取り組んでいます。準備が整い次第、翻訳版を提供いたしますので、もう少しお待ちください。

Migrate from snapshot events to thin events

Learn how to migrate to thin events without disrupting production.

プライベートプレビュー

Thin events for API v1 resources are available in private preview. Previously, thin events only supported API v2 resources. Learn more and request access.

Thin events provide a lightweight, version-stable alternative to snapshot events. Instead of receiving full resource objects in webhook payloads, you receive compact notifications and fetch the details you need. This eliminates the need to update webhook handlers when upgrading API versions.

Use this guide to migrate from snapshot events to thin events without disrupting production. The migration uses a dual-destination strategy where both handlers run in parallel during the transition. For a complete overview of thin events, including benefits and use cases, see Thin events.

Thin versions of snapshot events

To help with migration, Stripe creates thin event versions of your existing snapshot events. For example, customer.created has a thin version v1.customer.created. During migration, when a single action triggers both events, the thin version includes a snapshot_event field containing the original snapshot event ID. Use this as your idempotency key to prevent duplicate processing when running both handlers simultaneously.

はじめに

Make sure you have:

  • Access to your webhook endpoint code to add a new route
  • Permission to create event destinations in your Stripe Dashboard or with the API
  • Access to a sandbox or test mode to validate the migration before touching production
  • Monitoring and logging to compare behavior between handlers

警告

Complete this entire migration process in a sandbox or test mode before attempting it in live mode.

Phased migration strategy

The migration consists of the following phases:

  1. Add a new thin webhook route to your application.
  2. Create a thin event destination that mirrors your existing subscriptions.
  3. Run in shadow mode to validate behavior without making changes.
  4. Cut over with idempotency to handle both destinations simultaneously.
  5. Retire the snapshot destination after confirming stability.

This strategy ensures you don’t have any downtime and gives you multiple opportunities to validate and revert it if needed.

Add a thin webhook route

Create a new endpoint in your application specifically for thin events. Start with a minimal implementation that only verifies signatures and returns a 200 status code.

メモ

To access the snapshot_event field, configure your Stripe SDK to use a preview API version:

const stripe = require('stripe')(process.env.STRIPE_SECRET_KEY, { apiVersion: '2025-11-17.preview' });

Stable API versions (.clover) don’t include private preview features like snapshot_event.

const express = require('express'); const stripe = require('stripe')(process.env.STRIPE_SECRET_KEY, { apiVersion: '2025-11-17.preview' }); const app = express(); // New thin event endpoint app.post( '/webhook/thin', express.raw({type: 'application/json'}), async (req, res) => { const sig = req.headers['stripe-signature']; const thinWebhookSecret = process.env.THIN_WEBHOOK_SECRET; try { // Verify the signature using the same method as snapshot events const thinNotification = stripe.webhooks.constructEvent( req.body, sig, thinWebhookSecret ); console.log(`Verified thin event: ${thinNotification.id}`); // For now, just acknowledge receipt res.sendStatus(200); } catch (err) { console.log(`Webhook Error: ${err.message}`); res.status(400).send(`Webhook Error: ${err.message}`); } } ); app.listen(3000, () => console.log('Running on port 3000'));

Deploy this change and verify that the endpoint is accessible.

Create a thin event destination

In the Stripe Dashboard or API, create a new event destination configured for thin events.

Use the Dashboard

  1. Go to Developers > Webhooks.
  2. Click Add destination.
  3. Under Advanced settings, toggle on Use thin events.
  4. Subscribe to the same event types your snapshot destination uses, but with the v1. prefix:
    • customer.created → v1.customer.created
    • payment_intent.succeeded → v1.payment_intent.succeeded
    • invoice.paid → v1.invoice.paid
  5. Enter your new endpoint URL (for example, https://yourdomain.com/webhook/thin)

Use the API

Command Line
curl https://api.stripe.com/v2/core/event_destinations \ -H "Authorization: Bearer sk_test_..." \ -H "Stripe-Version: 2025-11-17.preview" \ -d "name"="Thin Events Destination" \ -d "type"="webhook_endpoint" \ -d "event_payload"="thin" \ -d "webhook_endpoint[url]"="https://yourdomain.com/webhook/thin" \ -d "enabled_events[]"="v1.customer.created" \ -d "enabled_events[]"="v1.payment_intent.succeeded" \ -d "enabled_events[]"="v1.invoice.paid"

メモ

Store the new webhook signing secret separately from your snapshot webhook secret. Label them clearly (for example, SNAPSHOT_WEBHOOK_SECRET and THIN_WEBHOOK_SECRET) to avoid mixing them up.

At this point, both destinations are active and delivering events to your application.

Run in shadow mode

Update your thin webhook handler to fetch event details and related objects—but don’t write to your database yet. Instead, log what actions you would take so you can monitor that your thin handler behaves the same as your snapshot handler.

Add these components to your handler from step 1:

  1. Enable a shadow mode flag (add it at the top of your file):
// Enable shadow mode via environment variable const SHADOW_MODE = process.env.SHADOW_MODE !== 'false';
  1. Fetch full event details (add after signature verification):
// Fetch the full event details const event = await stripe.v2.core.events.retrieve(thinNotification.id); console.log(`Processing ${event.type} (thin ID: ${event.id})`); // For interop events, log the original snapshot event ID if (event.snapshot_event) { console.log(`Correlated snapshot event: ${event.snapshot_event}`); }
  1. Fetch related object and shadow log (add event handling logic):
// Fetch the related object if needed if (event.type === 'v1.customer.created') { const customer = await stripe.customers.retrieve(event.related_object.id); if (SHADOW_MODE) { // SHADOW MODE: Log what you would do, but don't do it yet console.log(`[SHADOW] Would create customer record: ${customer.id}`); // recordMetric('customer.created.thin', 1); } else { // Production mode: Actually write to database await createCustomerInDatabase(customer); } }
  1. Complete the handler with shadow mode:
const SHADOW_MODE = process.env.SHADOW_MODE !== 'false'; // ← NEW app.post( '/webhook/thin', express.raw({type: 'application/json'}), async (req, res) => { const sig = req.headers['stripe-signature']; const thinWebhookSecret = process.env.THIN_WEBHOOK_SECRET; try { const thinNotification = stripe.webhooks.constructEvent( req.body, sig, thinWebhookSecret ); // ← NEW: Fetch full event details const event = await stripe.v2.core.events.retrieve(thinNotification.id); console.log(`Processing ${event.type} (thin ID: ${event.id})`); // ← NEW: Log snapshot event ID for correlation if (event.snapshot_event) { console.log(`Correlated snapshot event: ${event.snapshot_event}`); } // ← NEW: Fetch related object and handle in shadow mode if (event.type === 'v1.customer.created') { const customer = await stripe.customers.retrieve(event.related_object.id); if (SHADOW_MODE) { console.log(`[SHADOW] Would create customer record: ${customer.id}`); } else { await createCustomerInDatabase(customer); } } res.sendStatus(200); } catch (err) { console.log(`Webhook Error: ${err.message}`); res.status(400).send(`Webhook Error: ${err.message}`); } } );

What to look for in shadow mode

Run shadow mode for at least 24-48 hours and monitor:

  • Signature verification: Confirm all thin events verify successfully.
  • Fetch latency: Measure how long it takes to retrieve events and related objects.
  • Behavior consistency: Compare shadow logs against your snapshot handler’s actual behavior.
  • Error rates: Watch for any unexpected failures or missing data.

If your thin handler’s shadow logs diverge from what the snapshot handler actually does, investigate and fix the discrepancy now (while there’s no production impact).

メモ

For interop events, the snapshot_event field contains the original snapshot event ID. Log both the thin event.id and event.snapshot_event to correlate events across both handlers during the transition.

Cut over with idempotency

When shadow mode runs cleanly, enable real writes in your thin handler. Keep both destinations active during a brief overlap window to make sure you don’t miss any events.

Implement idempotency

During the overlap, you might receive the same logical event twice: once as a snapshot event and once as a thin event. Use idempotency keys to prevent duplicate processing.

メモ

The snapshot_event field in thin interop events contains the original snapshot event ID. By using this field as your idempotency key, both handlers can deduplicate against the same key.

First, set up an idempotency database table:

CREATE TABLE event_idempotency ( idempotency_key TEXT PRIMARY KEY, event_type TEXT NOT NULL, processed_at INTEGER NOT NULL );

Next, implement the idempotency helper:

// Try to insert idempotency key. Returns true if successfully inserted, false if duplicate. function tryInsertIdempotencyKey(idempotencyKey, eventType) { try { db.prepare(` INSERT INTO event_idempotency (idempotency_key, event_type, processed_at) VALUES (?, ?, ?) `).run(idempotencyKey, eventType, Date.now()); return true; // Insert succeeded - event is new } catch (err) { if (err.code === 'SQLITE_CONSTRAINT') { return false; // Duplicate - event already processed (use your database-specific error code for unique constraint violations) } throw err; // Re-throw unexpected errors } }

Update both handlers to use this pattern. First, the snapshot handler:

app.post( '/webhook/snapshot', express.raw({type: 'application/json'}), (req, res) => { const sig = req.headers['stripe-signature']; const snapshotWebhookSecret = process.env.SNAPSHOT_WEBHOOK_SECRET; try { const event = stripe.webhooks.constructEvent( req.body, sig, snapshotWebhookSecret ); // For snapshot events, idempotency key is just the event ID const idempotencyKey = event.id; // Try to insert idempotency key. If fails (duplicate), skip processing. if (!tryInsertIdempotencyKey(idempotencyKey, event.type)) { console.log(`Already processed: ${idempotencyKey}`); return res.sendStatus(200); } // Process the event if (event.type === 'customer.created') { createCustomerInDatabase(event.data.object); } res.sendStatus(200); } catch (err) { console.log(`Webhook Error: ${err.message}`); res.status(400).send(`Webhook Error: ${err.message}`); } } );

Next, the thin handler:

const SHADOW_MODE = process.env.SHADOW_MODE !== 'false'; app.post( '/webhook/thin', express.raw({type: 'application/json'}), async (req, res) => { const sig = req.headers['stripe-signature']; const thinWebhookSecret = process.env.THIN_WEBHOOK_SECRET; try { const thinNotification = stripe.webhooks.constructEvent( req.body, sig, thinWebhookSecret ); const event = await stripe.v2.core.events.retrieve(thinNotification.id); // For thin interop events, use snapshot_event if present, otherwise event ID const idempotencyKey = event.snapshot_event || event.id; // Check idempotency first to avoid unnecessary work if (!SHADOW_MODE) { // Try to insert idempotency key. If fails (duplicate), skip processing. if (!tryInsertIdempotencyKey(idempotencyKey, event.type)) { console.log(`Already processed: ${idempotencyKey}`); return res.sendStatus(200); } } // Process the event if (event.type === 'v1.customer.created') { const customer = await stripe.customers.retrieve(event.related_object.id); if (!SHADOW_MODE) { // Process the event createCustomerInDatabase(customer); } else { console.log(`[SHADOW] Would create customer: ${customer.id}`); console.log(`[SHADOW] Idempotency key: ${idempotencyKey}`); } } res.sendStatus(200); } catch (err) { console.log(`Webhook Error: ${err.message}`); res.status(400).send(`Webhook Error: ${err.message}`); } } );

How this prevents duplicates:

  1. When a customer is created, Stripe generates both events:

    • Snapshot event: evt_abc123
    • Thin event: evt_xyz789 with snapshot_event: "evt_abc123"
  2. Both handlers receive events simultaneously:

    • Snapshot handler: idempotencyKey = event.id > "evt_abc123"
    • Thin handler: idempotencyKey = event.snapshot_event || event.id → "evt_abc123"
  3. Both handlers try to insert the same key:

    • Snapshot handler: INSERT INTO ... VALUES ('evt_abc123', ...) > succeeds
    • Thin handler: INSERT INTO ... VALUES ('evt_abc123', ...) > unique constraint violation
  4. Result: The customer is created.

Monitor the cutover

With both handlers writing to your database:

  • Watch duplicate detection: Confirm that your idempotency logic catches overlapping events.
  • Compare write patterns: Make sure thin and snapshot handlers produce identical database states.
  • Track error rates: Alert on any increase in failures.
  • Monitor performance: Measure the impact of additional API calls to fetch related objects.

警告

If anything looks wrong, disable writes in the thin handler and investigate. Your snapshot handler remains your point of reference until you’re confident in the thin path.

Keep the overlap window short (a few hours to a day at most). This limits the period during which you process events twice, and it simplifies troubleshooting if issues arise.

Retire the snapshot destination

After the thin handler processes events reliably for a comfortable period of time, you can retire the snapshot destination.

Phase 1: Disable (keep code in place)

  1. In the Stripe Dashboard, go to Developers > Webhooks > Event destinations.
  2. Find your snapshot event destination.
  3. Click Disable.

This stops Stripe from sending events to your snapshot endpoint, but leaves your code in place as a safety measure. Monitor your thin-only flow to confirm stability.

Phase 2: Delete the snapshot

If everything remains stable:

  1. Delete the snapshot event destination from the Dashboard.
  2. Remove the snapshot webhook handler code from your application.
  3. Delete the SNAPSHOT_WEBHOOK_SECRET from your configuration.
  4. Update any documentation or runbooks that reference snapshot events.

Troubleshooting

Thin handler receives no events

Check your endpoint URL: Verify the thin destination points to the correct URL (for example, /webhook/thin, not /webhook).

Test locally: Use a tunneling tool such as ngrok to expose your local endpoint, then create a thin event destination pointing to that URL.

Signature verification fails

Check webhook secret: Make sure THIN_WEBHOOK_SECRET contains the signing secret from your thin event destination, not your snapshot destination.

Inspect raw payload: Signature verification requires the raw request body. Don’t parse the JSON before verifying:

// Correct: use express.raw() app.post('/webhook/thin', express.raw({type: 'application/json'}), handler); // Incorrect: express.json() parses the body app.post('/webhook/thin', express.json(), handler);

Recommendations

Migrate by event type

We recommend that you migrate event-by-event by subscribing to specific thin event types in your new destination. For example, start with v1.customer.created and v1.customer.updated, validate, then add more event types.

Dual handlers

We don’t recommend running dual handlers or letting them run indefinitely. Running dual handlers increases operational complexity, costs (bandwidth and processing), and increases the risk of divergent behavior. Complete the migration within a few weeks.

API upgrades after migration

A key benefit of thin events is that your webhook payload doesn’t change. The push notification remains stable, and you fetch versioned resource details when you need them using your current API version.

参照情報

  • Thin events overview
  • Event destinations
  • Webhook best practices
  • API versioning
このページはお役に立ちましたか。
はいいいえ
  • お困りのことがございましたら 、サポートにお問い合わせください。
  • 変更ログをご覧ください。
  • ご不明な点がございましたら、お問い合わせください。
  • LLM ですか?llms.txt を読んでください。
  • Powered by Markdoc