Testing and troubleshooting
Use this page to test subscription creation, webhook handling, payment failure, recovery, cancellation, and schedules before going live.
The goal is to prove that your backend can keep access aligned with confirmed billing state, not only that a customer can reach a success page.
What this page is for
This page helps you:
- test the Checkout-created subscription path;
- test direct subscription creation;
- verify invoice and payment events;
- diagnose common access and billing mistakes;
- collect the information Paypercut support needs when something is unclear.
Testing checklist
Before going live, confirm that your integration can do all of this:
- Store your internal user, account, workspace, or tenant ID mapped to
customer_id. - Store
subscription_id. - Store
price_idor your internal plan mapping. - Store relevant
invoice_idvalues for reconciliation. - Verify webhook signatures.
- Make webhook handling idempotent.
- Handle duplicate webhook events.
- Do not grant production access from the success URL alone.
- Grant or extend access from the verified paid-period signal.
- Handle payment failure.
- Test failed direct creation with
error_if_incomplete. - Test
past_duerecovery if usingdefault_incompleteorallow_incomplete. - Confirm the access policy you want for
trialing. - Confirm the access policy you want after cancellation until
current_period_end. - Confirm schedule behavior if you use schedules.
Test the Checkout-created path
Use this path when the customer enters or confirms payment details in Checkout.
Test:
- Create a Checkout Session with
mode: "subscription". - Use a recurring Price in
line_items. - Send the customer to hosted or embedded Checkout.
- Receive and verify
checkout_session.completed. - Retrieve or store the Checkout Session and linked
subscription. - Receive
invoice.paid. - Grant or extend access from the paid invoice.
- Confirm duplicate event delivery does not grant access twice.
- Confirm your success page can show a pending state before webhooks are processed.
Do not treat the success URL as paid access proof.
Test the direct-creation path
Use this path when your backend already has a Paypercut Customer and reusable payment method.
Test:
- Create or retrieve the Customer.
- Confirm the reusable payment method belongs to that Customer.
- Create a subscription with recurring items.
- Store the returned
subscription_idif a subscription is created. - Inspect the returned
status. - Process
invoice.paid. - Process
invoice.payment_failed. - Test
error_if_incompletewith a failing payment method so your backend handles402and no created subscription. - Test
default_incompleteorallow_incompleteif you allow a created subscription to enter recovery.
Do not use direct creation when the customer still needs to enter payment details.
Test invoice and payment events
For subscription access, invoice events are the primary signal.
Test these paths:
| Event | Test expectation |
|---|---|
invoice.paid |
Your backend maps the invoice to the subscription and grants or extends access once. |
invoice.payment_failed |
Your backend starts recovery and fetches latest state when needed. |
| duplicate event delivery | Your backend records the event or invoice and avoids duplicate access changes. |
| payment-level event | Your backend can use it for support or reconciliation without treating it as the subscription lifecycle source. |
Payment and PaymentIntent events are useful for debugging payment attempts. They should not replace invoice events for subscription access.
Test failed payment behavior
Test failure before you launch.
Important paths:
| Flow | Expected behavior |
|---|---|
Direct creation with error_if_incomplete |
Payment failure returns 402 and no subscription is created. |
Direct creation with default_incomplete |
Payment failure can create a subscription that needs recovery and may enter past_due. |
Direct creation with allow_incomplete |
Payment failure can create a subscription that needs recovery and may enter past_due. |
| Renewal failure | invoice.payment_failed starts recovery. Access depends on your product grace-period policy and latest state. |
| Resume failure | Resume returns 402; the subscription remains past_due. |
Your product should define what happens during failed-payment recovery: continued access, limited access, or blocked access.
Test cancellation and resume behavior
For cancellation, retrieve the subscription after the API action and inspect returned fields before changing customer-visible access.
Check:
status;current_period_end;cancel_at_period_end;cancel_at;canceled_at;ended_at;cancellation_details.
For resume, only use the resume endpoint for eligible past_due subscriptions using automatic collection. Confirm the customer has a valid default payment method before retrying collection.
Troubleshooting by symptom
Customer completed Checkout but has no access
What usually causes this
The frontend reached the success URL before your backend processed the server-side events, or your webhook handler did not map the Checkout Session to the internal account.
How to check
Inspect the Checkout Session ID, checkout_session.completed, linked subscription, invoice.paid, and your internal account mapping.
How to fix
Show a pending state on the success page and grant access only after your backend processes confirmed server-side state.
Related docs
Create subscriptions with Checkout
Customer has access from the success page but no paid invoice exists
What usually causes this
Access is being granted from the browser redirect instead of from invoice.paid.
How to check
Look for an invoice.paid event and an invoice whose parent links to the subscription.
How to fix
Move production access changes to your webhook handler. Let the success page show pending or confirmation UI.
Related docs
Invoices and payment collection
Checkout Session was created but no subscription was stored
What usually causes this
Your backend stored only the Checkout Session ID, or it did not retrieve the completed session after checkout_session.completed.
How to check
Retrieve the Checkout Session and inspect the subscription field when available. Check your database for the internal account to subscription_id mapping.
How to fix
Store checkout_session_id at creation and store subscription_id after Checkout completion or later retrieval.
Related docs
Subscription was created with the wrong customer
What usually causes this
The integration reused or passed the wrong Paypercut Customer ID for the internal account.
How to check
Compare your internal account ID, customer_id, Checkout Session customer, subscription customer, and invoice customer.
How to fix
Create a durable internal account to Customer mapping and use it consistently before creating Checkout Sessions, direct subscriptions, or schedules.
Related docs
Subscription Checkout fails because the price is not recurring
What usually causes this
The Checkout Session line item references a one-time Price instead of a recurring Price.
How to check
Inspect the Price used in line_items and confirm it has recurring billing configuration.
How to fix
Create or select a recurring Price for subscription Checkout.
Related docs
Quickstart: Create a subscription with Checkout
Direct subscription creation returns 402
What usually causes this
The request used payment_behavior: "error_if_incomplete" and the first payment failed.
How to check
Check the create request, the payment_behavior, and the error response. With error_if_incomplete, no subscription is created on payment failure.
How to fix
Ask the customer to update or replace the payment method, then retry with an idempotency key that matches your retry strategy.
Related docs
Direct subscription was created as past_due
What usually causes this
The first automatic collection attempt failed under a behavior that still allows a subscription to be created.
How to check
Retrieve the subscription and inspect status, payment_behavior, default_payment_method, and related invoice events.
How to fix
Start recovery from invoice.payment_failed. Confirm a valid default payment method before attempting resume.
Related docs
Webhook was not received
What usually causes this
The endpoint is not configured, is disabled, is subscribed to the wrong events, or is failing before returning success.
How to check
Inspect your webhook endpoint configuration, enabled event types, delivery logs, signature verification code, and HTTP responses.
How to fix
Enable the endpoint, subscribe to checkout_session.completed, invoice.paid, and invoice.payment_failed, verify signatures using the raw request body, and return success only after safe processing.
Related docs
Webhooks for subscription integrations
Webhook was received more than once
What usually causes this
Webhook delivery is retryable, so the same event can be delivered more than once.
How to check
Look for repeated event IDs, delivery IDs, or the same invoice ID with the same event type.
How to fix
Make webhook handlers idempotent. Store processed event IDs or invoice IDs before changing access.
Related docs
Webhooks for subscription integrations
invoice.paid was received but access was not extended
What usually causes this
The invoice was not mapped back to the subscription or internal account.
How to check
Inspect data.object.id, invoice parent.subscription_details.subscription, your subscription_id mapping, and your access update logs.
How to fix
Store subscription_id and invoice IDs, then make the invoice handler grant or extend the correct internal account idempotently.
Related docs
Invoices and payment collection
invoice.payment_failed was received but the customer was not put into recovery
What usually causes this
The webhook handler ignores failed invoice events or treats payment-level success/failure events as the only source of recovery state.
How to check
Inspect the webhook handler branch for invoice.payment_failed, the linked subscription, and whether your recovery notification or billing UI was triggered.
How to fix
Start recovery from invoice.payment_failed, fetch latest state when needed, and apply your product grace-period policy.
Related docs
Invoices and payment collection
Payment succeeded but the subscription state looks wrong
What usually causes this
The integration is looking only at a PaymentIntent or Payment event instead of the invoice and subscription state.
How to check
Inspect the invoice, invoice payment attempts, parent subscription details, and latest subscription status.
How to fix
Use payment-level data for support and reconciliation, but base subscription access on invoices and latest subscription state.
Related docs
Customer canceled but still has access
What usually causes this
Your product policy allows access until a period boundary, or your backend did not reconcile cancellation fields after the API action.
How to check
Retrieve the subscription and inspect status, current_period_end, cancel_at_period_end, cancel_at, canceled_at, and ended_at.
How to fix
Define your cancellation access policy, store the returned cancellation fields, and reconcile access from latest subscription state.
Related docs
Customer should have access through current_period_end, but access was revoked early
What usually causes this
The access system ended access from a cancellation request or failed payment without checking the paid billing period and product policy.
How to check
Inspect the latest paid invoice, current_period_end, cancellation fields, and your access expiration record.
How to fix
Use current_period_end and invoice events when your policy allows access through the paid period.
Related docs
Integrator stored the PaymentIntent ID but not the subscription ID
What usually causes this
The integration treated the first payment as the subscription lifecycle source.
How to check
Look for stored PaymentIntent IDs without a subscription_id mapping.
How to fix
Store subscription_id from Checkout completion, direct creation, or schedule activation. Use PaymentIntent IDs only for payment-level support and reconciliation.
Related docs
Integrator assumed public subscription.* webhooks exist
What usually causes this
The integration copied event names from internal systems or another provider instead of the public webhook enum.
How to check
Inspect your webhook enabled events and compare them to the public API reference. The beginner subscription contract uses Checkout, invoice, and payment events.
How to fix
Build around checkout_session.completed, invoice.paid, and invoice.payment_failed unless public subscription events are explicitly exposed for your account.
Related docs
Webhooks for subscription integrations
Integrator used send_invoice in a public subscription path
What usually causes this
The integration followed schema text that mentions invoice-based billing as coming soon.
How to check
Inspect subscription or schedule requests for collection_method: "send_invoice" or payment_behavior: "send_invoice".
How to fix
Use automatic collection with a reusable payment method. Do not build public subscription flows around send_invoice until it is supported.
Related docs
Schedule exists but no linked subscription exists yet
What usually causes this
The schedule has not reached the first phase start_date, so the subscription has not been created.
How to check
Retrieve the schedule and inspect status, start_date, next_action_at, and subscription.
How to fix
Store schedule_id first. Store subscription_id after activation, then use invoice events for access.
Related docs
Schedule cancellation behavior is unclear or unexpected
What usually causes this
The public API reference describes optional linked-subscription cancellation, while the verified implementation currently cancels a linked subscription when a schedule is manually canceled.
How to check
Retrieve the schedule and linked subscription after cancellation. Inspect schedule status, canceled_at, subscription status, canceled_at, and ended_at.
How to fix
Do not assume schedule cancellation is schedule-only. If you need the subscription to keep billing independently, verify release behavior for your account before canceling the schedule.
Related docs
Debugging checklist
When a subscription issue is reported, collect:
- Internal user, account, workspace, or tenant ID.
customer_id.checkout_session_id, if Checkout was used.subscription_id, if it exists.schedule_id, if a schedule was used.- Relevant
invoice_idvalues. - Relevant Payment or PaymentIntent IDs for support.
- Webhook event IDs or delivery IDs.
- Latest subscription
status. current_period_startandcurrent_period_end.payment_behavior.default_payment_method.- Recent request idempotency keys.
- Error response body and trace ID, if an API call failed.
Common mistakes
- Granting access from the success URL.
- Not storing
subscription_id. - Not verifying webhook signatures.
- Processing duplicate webhooks as new work.
- Ignoring
invoice.payment_failed. - Treating PaymentIntent events as subscription access proof.
- Using
send_invoicebefore it is supported. - Assuming
trialingalways grants access. - Assuming public
subscription.*webhooks exist. - Using schedules without storing
schedule_id. - Canceling a schedule without checking the linked subscription afterward.
What to send support
When contacting support, include:
- your merchant account ID;
- whether this is test mode or live mode;
- internal account or user reference;
customer_id;subscription_id, if available;schedule_id, if available;checkout_session_id, if available;- invoice IDs and webhook event IDs;
- API trace ID from the error response;
- the timestamp of the request or event;
- a short description of the expected access state and actual access state.
Do not send live secret keys, full payment credentials, or customer personal data that is not needed for the investigation.

