Skip to main content

API Errors

409 Conflict — Duplicate recovery request

{
  "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

{
  "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 — we can raise limits for legitimate workloads.

403 Forbidden — Transaction blocked by risk engine

{
  "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 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

{
  "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

{
  "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 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 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 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.

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
We respond within 4 business hours.