Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
89 changes: 61 additions & 28 deletions runbooks/replay-failed-events.py
Original file line number Diff line number Diff line change
@@ -1,41 +1,74 @@
import stripe
import os
import subprocess

"""
Purpose: Replay completly failed webhook events
Purpose: Replay completely failed (all retried exhausted) webhook events.

The above should not happen (due to retries- remember Stripe
automatically retries webhook event sends to the registered webhook
- but not forever!

The above should not happen (due to retries)
But if all retries fail, then eventually no more retries
If **all** retries fail, then eventually no more retries
are performed. (Aka do better monitoring/alerting)

See
USAGE:

# setup
python3 -m venv venv
. venv/bin/activate
pip install -r requirements.txt

# Configure .env / or export for:
# STRIPE_API_KEY
# WEBHOOK_ID

# run
. venv/bin/activate
python3 replay-failed-events.py

See also
https://docs.stripe.com/webhooks/process-undelivered-events?locale=en-GB
Why can't we simply get all events accross platform accounts without iterating stripe
account ids?
Answer: Product gap: https://insiders.stripe.dev/t/api-to-fetch-all-events-across-connected-accounts/471
"""

import stripe
import os
import subprocess


from dotenv import load_dotenv

# Settings
STRIPE_API_KEY = os.getenv("STRIPE_API_KEY")
load_dotenv(verbose=True) # take environment variables

# All subscribie shops sign-up through the 'master' subscribie shop
# because dogfooding https://en.wikipedia.org/wiki/Eating_your_own_dog_food
SUBSCRIBIE_MASTER_CONNECT_ACCOUNT_ID = os.getenv(
"SUBSCRIBIE_MASTER_CONNECT_ACCOUNT_ID"
) # noqa: E501
WEBHOOK_ID = os.getenv("WEBHOOK_ID") # the endpoint prod webhook router
STRIPE_API_KEY = os.getenv("STRIPE_API_KEY")

EVENTS_ENDING_BEFORE_EVENT_ID = os.getenv("EVENTS_ENDING_BEFORE_EVENT_ID")
# Each webhook event at the webhook events delivery page (insert link here)
# needs to extract the account id of the event from that failed webhook- it changes based on the shop it pertains to.
# Solution
# 1. List all connected accounts
# 2. For each connected account, list all their events
# 3. Iterate over all their events, if pending_webhooks > 0
# then replay the event

WEBHOOK_ID = os.getenv("WEBHOOK_ID") # the endpoint prod webhook router
stripe.api_key = STRIPE_API_KEY

events = stripe.Event.list(
ending_before=EVENTS_ENDING_BEFORE_EVENT_ID, # Specify an event ID that was sent just before the webhook endpoint became unavailable. # noqa:E501
# types=["payment_intent.succeeded", "payment_intent.payment_failed"], # all by default # noqa: E501
delivery_success=False,
stripe_account=SUBSCRIBIE_MASTER_CONNECT_ACCOUNT_ID,
)

for event in events.auto_paging_iter():
subprocess.run(
f"stripe events resend --live --api-key {STRIPE_API_KEY} --account={SUBSCRIBIE_MASTER_CONNECT_ACCOUNT_ID} {event.id} --webhook-endpoint={WEBHOOK_ID}", # noqa: E501
shell=True,
)

print("Finding connect accounts")
for connect_account in stripe.Account.list().auto_paging_iter():
# (Guard) Only process express account types
if connect_account.type != "express":
continue
if connect_account.charges_enabled != True:
continue
print(f"Processing account id: {connect_account.id}")
print(f"Processing account name: {connect_account.company['name']}")
# Get events for this connect account,
# Identify unsuccessfully delivered events to webhook(s)
# https://docs.stripe.com/webhooks/process-undelivered-events?locale=en-GB#list-events
for event in stripe.Event.list(stripe_account=connect_account.id, delivery_success=False).auto_paging_iter():
print(f"Identified unsuccessfully delivered event: {event.id} for shop {connect_account.id}")
print(f"Resending the event {event.id} for account {connect_account.id}, {connect_account.company['name']}")
redrive_command = f"stripe events resend --live --api-key {STRIPE_API_KEY} --account={connect_account.id} {event.id} --webhook-endpoint={WEBHOOK_ID}", # noqa: E501
print(f"Running: {redrive_command}")
subprocess.run(redrive_command, shell=True)
3 changes: 2 additions & 1 deletion runbooks/requirements.txt
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
stripe
stripe
python-dotenv
Loading