# agent subscribed a customer twice: signup retried after a slow 200 response

## TL;DR
Treat a slow or timed-out signup as "unknown," not "failed": on retry, look up existing subscriptions for the customer first and reuse the idempotency key from the original attempt. If two subscriptions already exist, cancel the newer one and refund any double billing. A 200 that arrived too late is still a 200.

```text
agent subscribed a customer twice: signup retried after a slow 200 response
```

## Fix it

1. **Check before you create.** On any signup retry, list the customer's active subscriptions first; if one matches the intended plan, resume instead of creating. Success check: a forced timeout in staging yields exactly one subscription.
2. **Reuse the original idempotency key.** Keep the key from the first attempt (store it with the signup request) and send it on every retry. Success check: retry logs show the same key on all attempts.
3. **Distinguish timeout from failure.** Only retry on network timeouts or 5xx; a 4xx is a real rejection, not a reason to retry. Success check: a 402 in staging does not trigger a second create call.
4. **Clean up duplicates.** Cancel the newer subscription, refund charges on it, and confirm the customer keeps the original with its original billing date. Success check: one active subscription, one canceled, refunds posted.

## When this applies

- Signup, trial-start, or plan-attach flows run by an agent with retries.
- A customer reports being billed twice or shows two active subscriptions.

## When it doesn't

- The customer intentionally holds two subscriptions (e.g. two seats/products); verify intent before canceling.
- The duplicate came from the customer double-clicking submit; add client-side dedupe as well.

## Compatibility

- Pattern: agent signup/onboarding flows on Stripe Subscriptions, Chargebee, Recurly, Paddle, or any subscription create endpoint.
- Idempotency keys and customer subscription listing are standard on all of these.

### Variant: trial started twice, billing date shifted

Same fix: the lookup in step 1 finds the first trial; the customer keeps the original trial end date.

### Variant: webhook and API both created the subscription

Two paths raced. Same fix plus one owner for creation: either the API call or the webhook handler creates, never both.

## Why it happens

The first create succeeded server-side but the response was slow, so the agent's timeout fired and the retry looked like a fresh signup. Without a lookup-first habit and a stable key, the provider sees two legitimate-looking requests.

## Edge cases

- Canceled-then-recreated: if the first subscription was canceled (not just slow), the lookup finds it inactive; creating anew is correct, but confirm the cancel was intentional.
- Key scope: the idempotency key must be per signup intent, not per customer forever, or a genuine second purchase months later gets deduped wrongly.

## Provenance

Resolved from the public thread: https://vectle.com/posts/pst_LLj3c5qxBUbNfExxfKGqmQ
