Stripe Connect — это другая экосистема вебхуков, отличная от обычного Stripe. Математика подписи та же. Всё остальное другое. Если вы охотились за тем, почему ваш Connect-обработчик молча игнорирует account.application.deauthorized, эта статья для вас.
Connect меняет, какие события вы получаете
Обычные вебхуки Stripe приходят из вашего платформенного аккаунта: charges, customers, subscriptions. Вебхуки Connect приходят из связанных аккаунтов и из Connect-специфичных событий жизненного цикла на вашей платформе. Список во многом пересекается, но включает события, которые вы видите только при включённом Connect:
account.updated— статус верификации, capabilities, требованияaccount.application.deauthorized— связанный аккаунт отозвал ваш доступcapability.updated— состояния активации payouts/transfersperson.created,person.updated— для Custom- и Express-аккаунтовpayout.failed,payout.paid— на связанном аккаунте, не на вашей платформе
Большинство команд обнаруживают это после деплоя. Локальное тестирование выявляет проблему за минуты.
Заголовок Stripe-Account меняет всё
Когда событие происходит на связанном аккаунте, Stripe прикрепляет заголовок Stripe-Account с ID связанного аккаунта (acct_xxx). Ваш обработчик должен маршрутизировать по этому заголовку, а не по тому, что внутри payload.
const connectedAccountId = req.headers['stripe-account'];
const event = stripe.webhooks.constructEvent(
rawBody,
req.headers['stripe-signature'],
endpointSecret,
);
// Now process the event in the context of connectedAccountId
await handleConnectEvent(event, connectedAccountId);
Если забыть прочитать заголовок, ваш обработчик трактует каждое событие Connect так, будто оно произошло на вашей платформе. Баги от этого паттерна обычно проявляются как «payout показывается не тому продавцу».
Тестирование Connect локально с PortPreview
Настройка такая же, как при обычном локальном тестировании Stripe, плюс один дополнительный шаг:
- Запустите ваше платформенное приложение локально.
- Запустите туннель:
npx portpreview 3000. - В дашборде Stripe добавьте URL туннеля как endpoint вебхука и отметьте опцию Events on Connected accounts. Это переключатель, который меняет весь поток событий.
- Используйте тестовый Express- или Custom-связанный аккаунт. Тестовый режим Stripe включает фейкового создателя аккаунта «Jenny Rosen», который генерирует реалистичные события.
- Триггерите события из тестового интерфейса Connect: завершите онбординг, запросите payout, деавторизуйте аккаунт.
Поток деавторизации — самый сложный
Когда связанный аккаунт отзывает доступ вашего приложения, Stripe генерирует account.application.deauthorized ровно один раз. Если ваш обработчик падает, возвращает 5xx или не подтверждает событие вовремя, Stripe повторяет — но связанный аккаунт уже ушёл. Ваши последующие API-вызовы для этого аккаунта возвращают 401.
Тщательно тестируйте поток деавторизации. Используйте тестовый режим Connect, чтобы деавторизовать тестовый аккаунт, захватить вебхук и прогнать логику очистки против захваченного payload. Повторяйте, пока путь не станет пуленепробиваемым.
Express против Standard против Custom
Тип аккаунта меняет, какие события person/capability вы получаете. Express- и Custom-аккаунты эмитят события person.*, потому что ваша платформа помогает завершить онбординг. Standard-аккаунты обрабатывают свой онбординг через размещённые Stripe экраны, поэтому вы видите меньше событий. Если вы меняете тип аккаунта посреди проекта — а люди это делают — ваш обработчик вебхуков нуждается в корректировке.
Что бы мы реально сделали
Для реального маркетплейса с аналитикой на уровне платформы и отчётностью по продавцам стройте два отдельных пути обработчика с первого дня: один для событий платформы (без заголовка Stripe-Account), один для событий связанных аккаунтов. Маршрутизируйте в начале функции. Это избегает 90% багов «это для нас или для продавца» позже.
Вебхуки Connect используют схему подписи Stripe, поэтому механика верификации идентична обычному тестированию вебхуков Stripe. По фону о математике подписи у провайдеров см. руководство по верификации подписи. Запишитесь в лист ожидания PortPreview, чтобы тестировать Connect со встроенным захватом и повтором.