API Errors
409 Conflict — Duplicate recovery request
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
retryAfter value. If you’re consistently hitting this, contact support@revtain.com — we can raise limits for legitimate workloads.
403 Forbidden — Transaction blocked by risk engine
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 for the full list.
401 Unauthorized — Missing or invalid API key
Causes:
- API key not sent in the
X-API-KEYheader - API key is from a different environment (test key on production endpoint, etc.)
- API key was rotated
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
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
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:
- Confirm you’re using the
webhookSigningSecretfrom onboarding (starts withwhsec_), not the API key (starts withrev_). - Hash the raw request body (not parsed JSON) with HMAC-SHA256.
- Use a timing-safe comparison (e.g.,
crypto.timingSafeEqualin Node,hmac.compare_digestin Python). - See the Webhooks Guide 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
200immediately, 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 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 therevtainTransactionId and look up current state via GET /api/recovery/status/:id 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.)
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_invoiceAPI — 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.
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.
Still Stuck?
Email 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