Common issues
Customer gets 403 Forbidden instead of expected access
Symptom: Authenticated customer with an active subscription receives
403 Forbidden instead of a successful response.
Causes and fixes:
-
Payment is overdue. Check the subscription's payment status via the API. If payment has failed and the grace period (default 3 days) has passed, access is blocked.
CodeFix: Either resolve the payment issue in Stripe, or adjust the grace period via
zuplo_max_payment_overdue_daysmetadata on the plan or customer. -
Customer is using the wrong API key. Each subscription generates its own key. If the customer has multiple subscriptions or regenerated their key, they may be using an old or unrelated key.
Fix: Have the customer check their active key in the Developer Portal → Subscriptions page.
-
Customer has no subscription. If the customer authenticated but never subscribed to a plan, they get 403 on monetized routes.
Fix: Direct the customer to subscribe to a plan (even a free tier).
Customer gets 403 Forbidden for quota exceeded
Symptom: Customer's requests are being blocked and the error detail mentions exceeding a meter limit.
Causes and fixes:
-
Separate rate-limiting policy is blocking them. If you have both a
MonetizationInboundPolicy(monthly quota) and a standalone rate-limiting policy (per-second/per-minute), the rate limiter may be triggering before the monetization quota is reached.Fix: Check if a per-second/per-minute rate limit is configured. The response body detail message will indicate which limit was hit.
-
Meter counting both successes and failures. The default
meterOnStatusCodesis"200-299", so only successful responses are metered. If you changed this to a broader range, failed requests (4xx, 5xx from your backend) count against the quota.Fix: Set
meterOnStatusCodesto"200-299"to only count successful responses. -
Multiple meters consuming the same quota. If a single request increments a meter by more than 1 (e.g.,
"api_credits": 10), the quota depletes faster than the customer expects.Fix: Verify the meter increment values in your policy configuration match what your pricing page communicates.
Stripe Checkout redirects but subscription isn't created in Zuplo
Symptom: Customer completes Stripe Checkout successfully, but the Developer Portal shows no active subscription.
Causes and fixes:
-
Preview environment mismatch. If you tested in a preview deployment but the checkout was configured for a different environment, the subscription may have been created in the wrong bucket.
Fix: Ensure you are testing against the correct environment (working copy, preview, or production).
-
Stripe test mode / live mode mismatch. Products and subscriptions in Stripe test mode are invisible to live mode and vice versa.
Fix: Verify you're looking at the correct mode in both Stripe and Zuplo.
Usage dashboard shows zero despite active API traffic
Symptom: Customer is making successful API requests, but the usage dashboard in the Developer Portal shows 0 usage.
Causes and fixes:
-
Monetization policy is not applied to the route. The
MonetizationInboundPolicymust be in the inbound policy pipeline for metering to occur. If the route only has a different authentication policy (no monetization policy), requests go through but aren't metered.Fix: Verify the
monetization-inboundpolicy is listed in the route's inbound policies. -
meterOnStatusCodesexcludes the response status. If set to"200"but your API returns201 Created, those requests won't be metered.Fix: Widen the status code range (e.g.,
"200-299").
Plan changes don't take effect
Symptom: Customer upgrades their plan in the Developer Portal, but their entitlements don't change.
Causes and fixes:
-
Plan was updated but not published. Plans in
draftstatus aren't visible to customers, and updates to active plans need to be re-published.Fix: Check the plan status. Publish if it's still in draft.
-
Caching lag. Entitlement changes propagate in near-real-time, but there can be a brief propagation window (typically under 60 seconds based on
cacheTtlSeconds).Fix: Wait a minute and retry. If the issue persists, check the subscription via the API to verify the plan change was recorded.
Debugging tools
Check subscription state via the API
Code
Check meter usage
Code
Check Stripe webhook delivery
- Open Stripe Dashboard → Developers → Webhooks
- Click on the Zuplo webhook endpoint
- Review recent events — look for failed deliveries (red)
- Click on individual events to see the request/response details
Test with cURL
Verify the full flow manually:
Code
FAQ
Can I have both free and paid plans?
Yes. Create a free plan with price: null and isSoftLimit: false on the
entitlement. Free plans that don't require a credit card skip Stripe Checkout
entirely — the customer gets access immediately after clicking "Start Free."
Can a customer have multiple subscriptions?
Yes. Each subscription generates its own API key. The customer uses the appropriate key for each subscription. This supports scenarios like a primary subscription plus a credit pack, or separate subscriptions for different API products.
What happens if Stripe is down?
Zuplo caches subscription and quota state locally. If Stripe is temporarily unavailable, existing customers continue to have access based on their cached subscription state. New subscriptions and plan changes require Stripe to be available.
Can I use currencies other than USD?
Yes. Plans support any ISO 4217 currency code. Set the currency field when
creating a plan. You can offer the same plan in multiple currencies by creating
separate plan objects (e.g., pro-usd and pro-eur).
How are overages calculated?
Overages are modeled using graduated tiered pricing. The first tier covers the included allowance at a flat price, and subsequent tiers charge per-unit for usage beyond the allowance. See Pricing Models for details.
Can I set spending limits for pay-as-you-go customers?
You can set a hard entitlement limit that acts as a spending cap (e.g., max 100,000 requests regardless of willingness to pay). Alternatively, use a custom policy to check current usage against a configurable limit, or set up Stripe billing alerts to notify customers approaching a threshold.
Do meters count retried requests?
Yes. Each request that hits the gateway is independently metered. If a client
retries a failed request, both the original and retry are metered (subject to
meterOnStatusCodes filtering). This is by design — your infrastructure handled
both requests.
Can I migrate from Amberflo/Moesif/OpenMeter?
Yes. The third-party metering integrations (Amberflo, Moesif, OpenMeter policies) are separate from the native Zuplo Monetization. You can run them in parallel during migration:
- Add the
MonetizationInboundPolicyalongside your existing third-party policy - Let both meter simultaneously for a comparison period
- Once you've verified parity, remove the third-party policy
- Decommission the third-party metering service
Is there an SLA on metering accuracy?
Zuplo meters in the request path — every request that gets a response is metered
synchronously. There's no batch processing, no eventual consistency window, and
no sampling. If the customer's API call succeeded (based on
meterOnStatusCodes), it's counted.
Can I customize the 403 response body?
Yes. Use a custom outbound policy to transform the 403 response when it's a
quota error:
Code