Skip to content
fixerror.dev

Stripe vs PayPal: Error Codes Compared (2026)

Both Stripe and PayPal process billions in payments, but their error semantics diverge in ways that bite developers migrating between them or supporting both. This page maps the equivalent error codes side-by-side.

Service A
Stripe
Payments API
Service B
PayPal
Payments platform

Key differences

Area Stripe PayPal
Decline reasons Granular: 30+ decline_code values (insufficient_funds, lost_card, do_not_honor, etc.) on top of card_declined. Coarse: a handful of high-level errors (PAYER_CANNOT_PAY, INSTRUMENT_DECLINED, PAYEE_BLOCKED). Reasons sometimes only visible in PayPal dashboard.
Webhook signature HMAC-SHA256 with timestamp, verified via Stripe-Signature header. Rejects replay attacks via 5-min tolerance. Asymmetric (PayPal signs with their cert, you verify against their public key). More moving parts, more failure modes.
Idempotency Idempotency-Key header on any POST. Replays same response for 24h. PayPal-Request-Id header. Idempotency window varies by API.
Rate limits 100 RPS read / 100 RPS write per account. 429 with Retry-After. No documented hard rate limit; throttling kicks in but limit isn't published. Errors as 429 or 500.
3D Secure / SCA PaymentIntents with automatic SCA. Returns requires_action with next_action URL. SCA handled in the PayPal-hosted flow — your integration sees the result, not the in-progress challenge.
Test mode Separate test API keys. Real card numbers rejected; specific test cards trigger specific errors. Sandbox uses separate environment + sandbox accounts. Test buyers/sellers required.

Equivalent error codes

Card declined
card_declined (with decline_code: insufficient_funds) in Stripe
INSTRUMENT_DECLINED in PayPal
Stripe gives you the decline_code; PayPal usually does not. Surface a generic "try another method" message on PayPal.
Webhook signature mismatch
No signatures matching the expected signature were found in Stripe
VALIDATION_ERROR (verify-webhook-signature) in PayPal
Both can fail when payload is mutated by middleware. Verify against the raw body, not the parsed JSON.
Rate limit
rate_limit (HTTP 429) in Stripe
RATE_LIMIT_REACHED (HTTP 429) in PayPal
Both honour Retry-After. Stripe is more transparent about quotas.
Authentication
authentication_required (live key in test, expired, etc.) in Stripe
AUTHENTICATION_FAILURE (invalid client_id/secret) in PayPal
Stripe uses static API keys; PayPal uses OAuth bearer tokens that expire (~9h). Token refresh logic is a common PayPal gotcha.

When you support both Stripe and PayPal, you’re maintaining two parallel error catalogues with overlapping but non-identical semantics. The biggest gotcha: Stripe is unusually generous with decline metadata (decline_code: insufficient_funds), while PayPal often returns only a generic INSTRUMENT_DECLINED and forces customers to investigate via PayPal directly.

For new integrations, lean on Stripe’s structured errors for in-app messaging and treat PayPal as a “send the customer to PayPal to resolve” pattern.