Skip to Content

Cancel Subscription

POST/subscriptions/:id/cancel

Description

Cancel an active subscription as the merchant. The Exodus API submits the on-chain cancelWithSig transaction; Exodus pays the gas.

This is a signed request — call signCancelSubscription on your CheckoutSigner, then post the returned signature in the X-Signature header. The signer binds the current charge_nonce and a deadline into the EIP-712 typed-data digest; the API reads the nonce from its indexed on-chain state, and the request body carries the deadline so the on-chain contract can enforce the same expiry the signer used.

For customer-initiated cancels (the customer pays gas), surface the hosted https://checkout.exodus-int.com/cancel/:subscription_id URL instead.

Headers

HeaderDescriptionRequired
AuthorizationBearer token with your API keyyes
Content-Typeapplication/jsonyes
X-SignatureSignature from `signCancelSubscription(...)`yes

Path Parameters

NameTypeDescriptionRequired
idstringThe on-chain subscription ID (bytes32 hex, e.g. `0x9f3a...`). Obtain it from `GET /subscriptions`, the `subscription.created` webhook, or the `subscription_checkout.completed` webhook.yes

Body Parameters

NameTypeDescriptionRequired
deadlinenumberUnix timestamp (seconds) after which the signature expires. Computed by the SDK; returned in `body.deadline` from `signCancelSubscription`.yes

Producing the Signature

cancel-subscription.js
import { CheckoutSigner } from '@exodus/checkout-signer';
 
const signer = new CheckoutSigner();
 
const sub = await fetch(
  'https://checkout-api.exodus-int.com/subscriptions/0x9f3a4b5c6d7e8f9a0b1c2d3e4f5a6b7c8d9e0f1a2b3c4d5e6f7a8b9c0d1e2f3a',
  {
    headers: { Authorization: `Bearer ${process.env.API_KEY}` },
  }
).then((r) => r.json());
 
// The client defaults the deadline and returns it in `body`
const { signature, body } = signer.signCancelSubscription(sub);
 
const response = await fetch(`https://checkout-api.exodus-int.com/subscriptions/${sub.id}/cancel`, {
  method: 'POST',
  headers: {
    Authorization: `Bearer ${process.env.API_KEY}`,
    'Content-Type': 'application/json',
    'X-Signature': signature,
  },
  body: JSON.stringify(body),
});

Response

CANCEL SUBMITTED
{
  "subscription_id": "0x9f3a4b5c6d7e8f9a0b1c2d3e4f5a6b7c8d9e0f1a2b3c4d5e6f7a8b9c0d1e2f3a",
  "status": "cancelling",
  "tx_hash": "0xabc..."
}

The subscription transitions to cancelling as soon as the cancel transaction is confirmed on-chain. The indexer transitions it to cancelled once the SubscriptionCancelled event is ingested.

If the subscription is already cancelled or cancelling, the call is a no-op and returns 200 with tx_hash: null and the existing status.

Errors

StatusCodeDescription
400SignatureExpireddeadline had elapsed by the time the contract validated the signature.
400InvalidNonceSignature was produced against a stale charge_nonce. Re-fetch and re-sign.
400InvalidSignatureContract rejected the signature.
400OnChainRevertReceipt reverted with an unrecognized code.
401(no code)Signature does not recover to your registered signing address (error.type: "authentication_error", API-level preflight).
404not_foundSubscription ID does not exist.

Webhook Fired

subscription.cancelled — fires once on the cancelling → cancelled transition (when the on-chain SubscriptionCancelled event is indexed).

Start building

XO

Request Demo

Schedule a call with our team

Select a product
Arrow right

Start building
Grateful

Contact Us

We're here to help