> ## Documentation Index
> Fetch the complete documentation index at: https://docs.revtain.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Troubleshooting

> Common errors and how to fix them.

## API Errors

### `409 Conflict` — Duplicate recovery request

```json theme={null}
{
  "error": "Duplicate recovery request.",
  "message": "A recovery attempt with this idempotencyKey has already been processed.",
  "existingTransactionId": "...",
  "existingStatus": "succeeded"
}
```

**Cause:** You sent the same `idempotencyKey` twice, OR the same card + amount within 5 minutes (velocity check).

**Fix:** This is **expected** and protects you from double-charging. Use the `existingTransactionId` to look up the original outcome — don't retry. If your retry logic is firing duplicate calls, ensure `idempotencyKey` is generated **once per logical retry attempt**, not on every code path.

***

### `429 Too Many Requests` — Rate limit exceeded

```json theme={null}
{
  "error": "Too many recovery requests. Rate limit: 60 requests per minute per API key.",
  "retryAfter": "60 seconds"
}
```

**Cause:** You exceeded 60 recovery requests per minute.

**Fix:** Implement exponential backoff using the `retryAfter` value. If you're consistently hitting this, contact [support@revtain.com](mailto:support@revtain.com) — we can raise limits for legitimate workloads.

***

### `403 Forbidden` — Transaction blocked by risk engine

```json theme={null}
{
  "error": "Transaction blocked by Revtain Risk Engine to protect Merchant Health.",
  "declineCode": "fraudulent",
  "action": "blocked"
}
```

**Cause:** The original decline code (e.g., `fraudulent`, `lost_card`, `pickup_card`) indicates the issuer wants this card stopped.

**Fix:** **Do not retry.** Flag the customer account for review. Retrying these will trigger chargebacks and damage your merchant standing. See [Decline Codes](/concepts/decline-codes) for the full list.

***

### `401 Unauthorized` — Missing or invalid API key

**Causes:**

* API key not sent in the `X-API-KEY` header
* API key is from a different environment (test key on production endpoint, etc.)
* API key was rotated

**Fix:** Confirm the header is `X-API-KEY` (not `Authorization` or `X-Api-Key`). Check your environment variables. If rotated, update your secret store and redeploy.

***

### `202 Accepted` — Queued for retry

```json theme={null}
{
  "message": "Insufficient funds. Queued for Smart-Time Retry at 2026-04-04T06:00:00.000Z."
}
```

**Cause:** The decline code suggests retrying at a later time will improve recovery odds (e.g., `insufficient_funds` retried after payday).

**Fix:** This is **expected** — not an error. Wait for the webhook (`recovery.success` or `recovery.failed`) to confirm the final outcome. Don't poll the status endpoint aggressively; the webhook is the source of truth.

***

### `202 Accepted` with `status: "unknown"` — Ambiguous gateway response

```json theme={null}
{
  "status": "unknown",
  "message": "Recovery attempt result unknown. The gateway returned an ambiguous response."
}
```

**Cause:** The gateway returned a 5xx error or timed out. The charge may or may not have gone through.

**Fix:** Check your gateway dashboard to confirm. The async outcome webhook will eventually fire with the final state. **Do not retry on the same idempotency key** — you risk a double-charge.

## Webhook Issues

### Webhook returning `401` to my own endpoint

**Cause:** Signature mismatch. Either you're hashing the wrong payload, or your `webhookSigningSecret` is wrong.

**Fix:**

1. Confirm you're using the `webhookSigningSecret` from onboarding (starts with `whsec_`), not the API key (starts with `rev_`).
2. Hash the **raw request body** (not parsed JSON) with HMAC-SHA256.
3. Use a timing-safe comparison (e.g., `crypto.timingSafeEqual` in Node, `hmac.compare_digest` in Python).
4. See the [Webhooks Guide](/guides/webhooks#code-examples) for verified code samples.

***

### Webhook never arrives

**Causes & fixes:**

* **Endpoint isn't HTTPS** → Revtain only delivers to HTTPS URLs.
* **Endpoint returned non-200** → Revtain retries: three immediate attempts (0s, 2s, 8s), then a durable schedule (1m, 5m, 30m, 2h, 24h). Only after all of those fail is the event written to our audit log for manual resend. Check your logs for the earlier attempts.
* **Endpoint took longer than 10 seconds** → Treated as failure. Acknowledge the webhook with `200` immediately, then process async.
* **Wrong URL registered** → Re-check your onboarding settings.

***

### Recovery failed but I never got a `recovery.failed` webhook

**Cause:** Your account is configured with `dunningEnabled: false`. With dunning disabled, Revtain fires `recovery.success` and `recovery.blocked` webhooks, but does **not** fire `recovery.failed` — the assumption is that you're handling failure detection on your own side.

**Fix:** Either re-enable `dunningEnabled` so failure webhooks fire, or look up the outcome yourself via [`GET /api/recovery/status/:id`](/api-reference) using the `transactionId` returned from `/api/recovery/execute`. To change the setting, contact your Revtain account manager.

***

### Webhook arrives but content seems stale

**Cause:** You may be reading from a cached version, or the event was retried after a delay.

**Fix:** Always trust the `revtainTransactionId` and look up current state via [`GET /api/recovery/status/:id`](/api-reference) if uncertain.

## Connector Issues

### Connector fires but no recovery happens

**Cause:** Revtain couldn't extract the gateway token from your billing platform's API.

**Common reasons:**

* API key has insufficient permissions (see your connector's required permissions)
* Customer has no payment source on file
* Test/live mode mismatch (test webhook hitting live gateway, etc.)

**Fix:** Check the Revtain admin dashboard logs. Each connector failure is logged with the specific reason.

***

### Invoice not marked as paid after success

**Cause:** The recovery succeeded on the gateway but the connector couldn't update the billing platform invoice afterwards.

**Fix:**

* For **Chargebee** and **Recurly**, this happens via API automatically — confirm your platform API key has write permissions on invoices.
* For **Stripe Billing**, the invoice is paid via Stripe's `pay_invoice` API — check the invoice timeline in the Stripe Dashboard.
* For **Braintree**, the subscription auto-detects the recovered payment.
* For **Shopify (ReCharge)**, the charge is captured via the ReCharge API — confirm your ReCharge access token has charge-capture scope.

If you still see issues, check that the connector has write permissions on invoices/transactions.

## Testing Issues

### `simulateOutcome` is being ignored

**Cause:** Your primary gateway is **not** a Test Gateway. The `simulateOutcome` parameter is **only honoured** with test gateways.

**Fix:** Onboard a test gateway (Revtain Test Gateway, Stripe `sk_test_...`, Braintree Sandbox) for sandbox testing. In production, `simulateOutcome` is silently ignored.

***

### Test card declined but I expected success

**Cause:** Test cards (`4111 1111 1111 1111`, etc.) only succeed on test gateways. They're rejected by live gateways.

**Fix:** Confirm your primary gateway is in test mode. See [Testing Guide](/guides/testing).

## Still Stuck?

Email [support@revtain.com](mailto:support@revtain.com) with:

* Your client ID (from onboarding)
* The `transactionId` (if you have one)
* The full request payload (redact card numbers)
* The full response body
* Approximate timestamp

We respond within 4 business hours.
