Outbox creates reuse Stripe idempotency keys across database lifetimes and are refused for good #147

Closed
opened 2026-09-27 23:31:00 +00:00 by cgalo5758 · 1 comment
Owner

What happens

The outbox sends each Stripe create with the idempotency key member-console-outbox-<entry id> (outboxIdempotencyKey, internal/integrations/stripe/workflows/outbox.go). The entry id restarts wherever the outbox's sequence restarts: a database reset (the local demo reset does it on every make screens), a restore from a backup, or a second database on the same Stripe account. Stripe keeps a key for 24 hours and refuses a key reused with different parameters (idempotency_error). The entry is dead-lettered, and a retry sends the same key, so it can never succeed; the prices queued behind a refused product stay pending.

Seen locally: after a demo reset, Sync to Stripe on two demo products was refused, and their prices stayed unsynced, so no tier could be bought.

What should happen

A key is the same on every attempt of one entry and different for every entry that ever existed, in any database. Deriving it from the entry id and the entry's creation time keeps the first and gives the second without a migration.

Done when

  • Two entries with the same id and different creation times produce different keys; every attempt of one entry produces the same key.
  • A demo reset followed by Sync to Stripe within 24 hours of an earlier sync succeeds.
## What happens The outbox sends each Stripe create with the idempotency key `member-console-outbox-<entry id>` (`outboxIdempotencyKey`, `internal/integrations/stripe/workflows/outbox.go`). The entry id restarts wherever the outbox's sequence restarts: a database reset (the local demo reset does it on every `make screens`), a restore from a backup, or a second database on the same Stripe account. Stripe keeps a key for 24 hours and refuses a key reused with different parameters (`idempotency_error`). The entry is dead-lettered, and a retry sends the same key, so it can never succeed; the prices queued behind a refused product stay pending. Seen locally: after a demo reset, Sync to Stripe on two demo products was refused, and their prices stayed unsynced, so no tier could be bought. ## What should happen A key is the same on every attempt of one entry and different for every entry that ever existed, in any database. Deriving it from the entry id and the entry's creation time keeps the first and gives the second without a migration. ## Done when - Two entries with the same id and different creation times produce different keys; every attempt of one entry produces the same key. - A demo reset followed by Sync to Stripe within 24 hours of an earlier sync succeeds.
cgalo5758 added the
kind
bug
area/billingarea/integrations
labels 2026-09-27 23:31:00 +00:00
Author
Owner

Fixed in d862b46. The key now carries the entry's creation time as well as its id: the same on every attempt of one entry, different for every entry that ever held the id, so a reset, a restore or a second database no longer reuses a refused key. No migration.

Fixed in d862b46. The key now carries the entry's creation time as well as its id: the same on every attempt of one entry, different for every entry that ever held the id, so a reset, a restore or a second database no longer reuses a refused key. No migration.
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: wiki-cafe/member-console#147