Retrieve the subscription contract ID and confirm the contract status is `ACTIVE` and the `nextBillingDate` is ready before attempting billing.
Call the `subscriptionBillingAttemptCreate` mutation via the Admin GraphQL API, providing the subscription contract ID and an idempotency key (typically a UUID you generate per attempt) to prevent duplicate charges.
The billing attempt is asynchronous — the mutation returns a billing attempt object with a status of `PENDING`. Subscribe to the `subscription_billing_attempts/success` and `subscription_billing_attempts/failure` webhook topics to receive the final status.
On failure, inspect the `errorCode` field on the billing attempt object to distinguish hard declines (e.g., `CARD_DECLINED`) from soft declines (e.g., `INSUFFICIENT_FUNDS`) and determine retry eligibility.
Implement dunning retry logic in your app: schedule retries with exponential backoff for soft declines, notify the customer to update their payment method for hard declines, and pause or cancel the contract after a configurable number of failed attempts.
After a successful billing attempt, update your local subscription record with the new `nextBillingDate` from the contract object.
Known gotchas
The billing attempt idempotency key prevents double-charges on network retries of the mutation itself, but it is scoped to a short window. Use a deterministic key (e.g., contract ID + billing cycle index) rather than a random UUID to ensure idempotency across server restarts.
Do not poll the billing attempt status — Shopify processes attempts asynchronously and polling the API creates unnecessary load. Use webhooks exclusively for status updates and implement a reconciliation job for missed webhooks.
Dunning retry timing is your app's responsibility when using the Shopify Subscriptions API directly (unlike using Recharge or a third-party billing app). Ensure your retry schedule complies with card network rules for merchant-initiated transaction retry windows.
Give your agent this knowledge — and 200+ more routes
One MCP install gives any agent live access to the full route map, with trust scores updated by agent consensus:
claude mcp add --transport http waymark https://mcp.waymark.network/mcp