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_id or your internal plan mapping.
  • Store relevant invoice_id values 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_due recovery if using default_incomplete or allow_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:

  1. Create a Checkout Session with mode: "subscription".
  2. Use a recurring Price in line_items.
  3. Send the customer to hosted or embedded Checkout.
  4. Receive and verify checkout_session.completed.
  5. Retrieve or store the Checkout Session and linked subscription.
  6. Receive invoice.paid.
  7. Grant or extend access from the paid invoice.
  8. Confirm duplicate event delivery does not grant access twice.
  9. 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:

  1. Create or retrieve the Customer.
  2. Confirm the reusable payment method belongs to that Customer.
  3. Create a subscription with recurring items.
  4. Store the returned subscription_id if a subscription is created.
  5. Inspect the returned status.
  6. Process invoice.paid.
  7. Process invoice.payment_failed.
  8. Test error_if_incomplete with a failing payment method so your backend handles 402 and no created subscription.
  9. Test default_incomplete or allow_incomplete if 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

How subscriptions work

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

Choose a flow

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

Create subscriptions directly

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

Lifecycle and statuses

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

Lifecycle and statuses

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

Manage subscriptions

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

Lifecycle and statuses

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

How subscriptions work

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

Create subscriptions directly

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

Subscription schedules

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

Subscription schedules

Debugging checklist

When a subscription issue is reported, collect:

  1. Internal user, account, workspace, or tenant ID.
  2. customer_id.
  3. checkout_session_id, if Checkout was used.
  4. subscription_id, if it exists.
  5. schedule_id, if a schedule was used.
  6. Relevant invoice_id values.
  7. Relevant Payment or PaymentIntent IDs for support.
  8. Webhook event IDs or delivery IDs.
  9. Latest subscription status.
  10. current_period_start and current_period_end.
  11. payment_behavior.
  12. default_payment_method.
  13. Recent request idempotency keys.
  14. 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_invoice before it is supported.
  • Assuming trialing always 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.