> ## Documentation Index
> Fetch the complete documentation index at: https://base-a060aa97-docs-b20-concepts.mintlify.site/llms.txt
> Use this file to discover all available pages before exploring further.

# subscription.revoke

> Revoke subscriptions from your backend using CDP server wallets

export const Button = ({children, disabled, variant = "primary", size = "medium", iconName, roundedFull = false, className = '', fullWidth = false, onClick = undefined}) => {
  const variantStyles = {
    primary: 'bg-blue text-black border border-blue hover:bg-blue-80 active:bg-[#06318E] dark:text-white',
    secondary: 'bg-white border border-white text-palette-foreground hover:bg-zinc-15 active:bg-zinc-30',
    outlined: 'bg-transparent text-white border border-white hover:bg-white hover:text-black active:bg-[#E3E7E9]'
  };
  const sizeStyles = {
    medium: 'text-md px-4 py-2 gap-3',
    large: 'text-lg px-6 py-4 gap-5'
  };
  const sizeIconRatio = {
    medium: '0.75rem',
    large: '1rem'
  };
  const classes = ['text-md px-4 py-2 whitespace-nowrap', 'flex items-center justify-center', 'disabled:opacity-40 disabled:pointer-events-none', 'transition-all', variantStyles[variant], sizeStyles[size], roundedFull ? 'rounded-full' : 'rounded-lg', fullWidth ? 'w-full' : 'w-auto', className];
  const buttonClasses = classes.filter(Boolean).join(' ');
  const iconSize = sizeIconRatio[size];
  return <button type="button" disabled={disabled} className={buttonClasses} onClick={onClick}>
      <span>{children}</span>
      {iconName && <Icon name={iconName} width={iconSize} height={iconSize} color="currentColor" />}
    </button>;
};

export const BaseBanner = ({content = null, id, dismissable = true}) => {
  const LOCAL_STORAGE_KEY_PREFIX = 'cb-docs-banner';
  const [isVisible, setIsVisible] = useState(false);
  const onDismiss = () => {
    localStorage.setItem(`${LOCAL_STORAGE_KEY_PREFIX}-${id}`, 'false');
    setIsVisible(false);
  };
  useEffect(() => {
    const storedValue = localStorage.getItem(`${LOCAL_STORAGE_KEY_PREFIX}-${id}`);
    setIsVisible(storedValue !== 'false');
  }, []);
  if (!isVisible) {
    return null;
  }
  return <div className="fixed bottom-0 left-0 right-0 bg-white py-8 px-4 lg:px-12 z-50 text-black dark:bg-black dark:text-white border-t dark:border-gray-95">
      <div className="flex items-center max-w-8xl mx-auto">
        {typeof content === 'function' ? content({
    onDismiss
  }) : content}
        {dismissable && <button onClick={onDismiss} className="flex-shrink-0 text-gray-400 hover:text-gray-600 dark:hover:text-gray-300 transition-colors" aria-label="Dismiss banner">
          ✕
        </button>}
      </div>
    </div>;
};

Defined in the [Base Account SDK](https://github.com/base/account-sdk)

<Note>
  **Node.js Only**: This function uses CDP (Coinbase Developer Platform) server wallets and is only available in Node.js environments. For browser/client-side applications, use [`prepareRevoke`](/base-account/reference/base-pay/prepareRevoke) instead.
</Note>

<Info>
  The `revoke` function cancels subscriptions automatically from your backend. It uses a CDP smart wallet as the subscription owner to execute the revocation transaction, handling all details including wallet management, transaction signing, and optional gas sponsorship.
</Info>

<Warning>
  **Treat subscription IDs as capability handles, not proof of ownership.** Prefer a server-stored ID bound to the authenticated user. Pass `expectedPayer` so the SDK rejects revocations for subscriptions that do not belong to that user. `revoke()` also requires the permission spender to match your CDP smart wallet.
</Warning>

## How It Works

When you call `revoke()`, the function:

1. Initializes a CDP client with your credentials
2. Retrieves the existing smart wallet (subscription owner)
3. Prepares the revoke transaction call (and verifies the permission spender matches that wallet)
4. Executes the revocation using the smart wallet
5. Optionally uses a paymaster for gas sponsorship
6. Returns the transaction hash

## Parameters

<ParamField body="id" type="string" required>
  The subscription ID (permission hash) returned from `subscribe()`. Prefer a server-stored ID bound to the authenticated user.

  **Pattern:** `^0x[0-9a-fA-F]{64}$`
</ParamField>

<ParamField body="testnet" type="boolean">
  Whether to use Base Sepolia testnet. Must match the network used in `subscribe()`. Default: false
</ParamField>

<ParamField body="cdpApiKeyId" type="string">
  CDP API key ID. Falls back to `CDP_API_KEY_ID` environment variable.
</ParamField>

<ParamField body="cdpApiKeySecret" type="string">
  CDP API key secret. Falls back to `CDP_API_KEY_SECRET` environment variable.
</ParamField>

<ParamField body="cdpWalletSecret" type="string">
  CDP wallet secret. Falls back to `CDP_WALLET_SECRET` environment variable.
</ParamField>

<ParamField body="walletName" type="string">
  Optional custom wallet name for the CDP smart wallet. Default: "subscription owner"

  <Note>
    **Use Default**: Most applications should omit this parameter and use the default. Only specify if you used a custom name in `getOrCreateSubscriptionOwnerWallet()`.
  </Note>
</ParamField>

<ParamField body="paymasterUrl" type="string">
  Paymaster URL for transaction sponsorship (gasless transactions). Falls back to `PAYMASTER_URL` environment variable.
</ParamField>

<ParamField body="expectedPayer" type="string">
  Optional subscriber wallet address. When set, the subscription's payer must match this address or `revoke()` throws.

  **Pattern:** `^0x[0-9a-fA-F]{40}$`
</ParamField>

<ParamField body="expectedSpender" type="string">
  Optional. Must match your CDP smart wallet if provided. `revoke()` already binds the permission spender to the executing CDP wallet automatically.

  **Pattern:** `^0x[0-9a-fA-F]{40}$`
</ParamField>

## Returns

<ResponseField name="result" type="RevokeResult">
  Revoke execution result.

  <Expandable title="RevokeResult properties">
    <ResponseField name="success" type="true">
      Always true on successful revocation.
    </ResponseField>

    <ResponseField name="id" type="string">
      Transaction hash of the revocation.
    </ResponseField>

    <ResponseField name="subscriptionId" type="string">
      The subscription ID that was revoked.
    </ResponseField>

    <ResponseField name="subscriptionOwner" type="Address">
      Address of the wallet that executed the revocation.
    </ResponseField>
  </Expandable>
</ResponseField>

## Setup

Before using `revoke()`, you need CDP credentials. Get them from the [CDP Portal](https://portal.cdp.coinbase.com/projects/api-keys).

Set as environment variables:

```bash theme={null}
export CDP_API_KEY_ID="your-api-key-id"
export CDP_API_KEY_SECRET="your-api-key-secret"
export CDP_WALLET_SECRET="your-wallet-secret"
```

Or pass directly as parameters (see examples below).

<RequestExample>
  ```typescript Basic Revoke with Environment Variables theme={null}
  import { base } from '@base-org/account/node';

  // Requires: CDP_API_KEY_ID, CDP_API_KEY_SECRET, CDP_WALLET_SECRET env vars
  const result = await base.subscription.revoke({
    id: storedSubscriptionId,
    expectedPayer: authenticatedUserAddress,
  });

  console.log(`Revoked subscription: ${result.id}`);
  console.log(`Transaction: ${result.id}`);
  ```

  ```typescript Revoke with Explicit Credentials theme={null}
  import { base } from '@base-org/account/node';

  const result = await base.subscription.revoke({
    id: '0x71319cd488f8e4f24687711ec5c95d9e0c1bacbf5c1064942937eba4c7cf2984',
    cdpApiKeyId: 'your-api-key-id',
    cdpApiKeySecret: 'your-api-key-secret',
    cdpWalletSecret: 'your-wallet-secret',
    testnet: false
  });

  console.log(`Subscription revoked: ${result.subscriptionId}`);
  ```

  ```typescript Revoke with Paymaster (Gasless) theme={null}
  import { base } from '@base-org/account/node';

  // Use a paymaster to sponsor gas fees
  const result = await base.subscription.revoke({
    id: subscriptionId,
    paymasterUrl: 'https://api.developer.coinbase.com/rpc/v1/base/your-key',
    testnet: false
  });

  console.log(`Gasless revocation: ${result.id}`);
  ```

  ```typescript Advanced: Custom Wallet Name (If Needed) theme={null}
  import { base } from '@base-org/account/node';

  // Only needed if you used a custom name in getOrCreateSubscriptionOwnerWallet()
  const result = await base.subscription.revoke({
    id: subscriptionId,
    walletName: 'premium-subscriptions-wallet', // Must match wallet creation
    testnet: false
  });
  ```
</RequestExample>

<ResponseExample>
  ```typescript Success Response theme={null}
  {
    success: true,
    id: "0x8f3d9e2a1b4c5d6e7f8a9b0c1d2e3f4a5b6c7d8e9f0a1b2c3d4e5f6a7b8c9d0e",
    subscriptionId: "0x71319cd488f8e4f24687711ec5c95d9e0c1bacbf5c1064942937eba4c7cf2984",
    subscriptionOwner: "0xYourSubscriptionOwnerWallet"
  }
  ```
</ResponseExample>

## Error Handling

Always wrap `revoke()` calls in try-catch blocks:

```typescript theme={null}
try {
  const result = await base.subscription.revoke({
    id: subscriptionId,
    testnet: false
  });
  
  console.log(`Successfully revoked: ${result.id}`);
  
  // Update your database
  await updateSubscriptionRecord(subscriptionId, {
    status: 'cancelled',
    revokedAt: new Date(),
    revocationTx: result.id
  });
  
} catch (error) {
  console.error(`Failed to revoke subscription: ${error.message}`);
  
  // Handle specific error cases
  if (error.message.includes('not found')) {
    console.log('Subscription already cancelled or not found');
  } else if (error.message.includes('CDP')) {
    console.log('CDP credentials issue');
  }
}
```

## Common Use Cases

<Tabs>
  <Tab title="User Cancellation Request">
    ```typescript theme={null}
    async function handleUserCancellation(userId: string, subscriptionId: string) {
      try {
        // Revoke the subscription
        const result = await base.subscription.revoke({
          id: subscriptionId,
          testnet: false
        });
        
        // Update database
        await db.updateSubscription(subscriptionId, {
          status: 'cancelled',
          cancelledBy: 'user',
          cancelledAt: new Date(),
          revocationTx: result.id
        });
        
        // Notify user
        await sendEmail(userId, {
          subject: 'Subscription Cancelled',
          body: `Your subscription has been successfully cancelled.`
        });
        
        return { success: true };
      } catch (error) {
        console.error('Cancellation failed:', error);
        throw error;
      }
    }
    ```
  </Tab>

  <Tab title="Policy Violation">
    ```typescript theme={null}
    async function revokeForViolation(subscriptionId: string, reason: string) {
      try {
        // Revoke immediately
        const result = await base.subscription.revoke({
          id: subscriptionId,
          testnet: false
        });
        
        // Log violation
        await db.logViolation({
          subscriptionId,
          reason,
          action: 'revoked',
          transactionHash: result.id,
          timestamp: new Date()
        });
        
        console.log(`Revoked subscription due to: ${reason}`);
      } catch (error) {
        console.error('Revocation failed:', error);
      }
    }
    ```
  </Tab>

  <Tab title="Administrative Action">
    ```typescript theme={null}
    async function bulkRevokeExpiredTrials() {
      const expiredTrials = await db.getExpiredTrialSubscriptions();
      
      for (const trial of expiredTrials) {
        try {
          const result = await base.subscription.revoke({
            id: trial.subscriptionId,
            testnet: false
          });
          
          await db.updateSubscription(trial.id, {
            status: 'trial_expired',
            revokedAt: new Date(),
            revocationTx: result.id
          });
          
          console.log(`✅ Revoked expired trial: ${trial.id}`);
        } catch (error) {
          console.error(`Failed to revoke trial ${trial.id}:`, error.message);
        }
      }
    }
    ```
  </Tab>
</Tabs>

## Common Errors

<AccordionGroup>
  <Accordion title="Missing CDP Credentials">
    ```
    Failed to initialize CDP client for subscription revoke
    ```

    **Solution**: Ensure `CDP_API_KEY_ID`, `CDP_API_KEY_SECRET`, and `CDP_WALLET_SECRET` are set as environment variables or passed as parameters.
  </Accordion>

  <Accordion title="Subscription Not Found">
    ```
    Subscription with ID 0x... not found
    ```

    **Solution**: Check that the subscription ID is correct. The subscription may have already been revoked or never existed.
  </Accordion>

  <Accordion title="Wallet Does Not Exist">
    ```
    Wallet "subscription owner" does not exist
    ```

    **Solution**: The CDP wallet hasn't been created yet. First call [`getOrCreateSubscriptionOwnerWallet`](/base-account/reference/base-pay/getOrCreateSubscriptionOwnerWallet) to set up the wallet.
  </Accordion>

  <Accordion title="Already Revoked">
    If a subscription is already revoked, calling `revoke()` again may fail. Check status first using [`getSubscriptionStatus`](/base-account/reference/base-pay/getStatus).
  </Accordion>
</AccordionGroup>

## Important Notes

<Warning>
  **Permanent Action**: Revoking a subscription is permanent and cannot be undone. The user would need to create a new subscription to re-enable charging.
</Warning>

<Note>
  **User-Initiated Revocation**: Users can also revoke subscriptions themselves from their wallet. Your backend should handle subscription status checks before attempting charges.
</Note>

## When to Use Revoke

Use `revoke()` from your backend when you need to programmatically cancel subscriptions:

* **User requests cancellation** through your application
* **Policy violations** or terms of service breaches
* **Failed payments** after multiple retry attempts
* **Account closure** or service termination
* **Trial period expiration** without conversion
* **Administrative actions** or bulk operations

## Related Functions

<CardGroup cols={3}>
  <Card title="Setup Wallet" icon="wallet" href="/base-account/reference/base-pay/getOrCreateSubscriptionOwnerWallet">
    Create CDP wallet before revoking
  </Card>

  <Card title="Check Status" icon="chart-line" href="/base-account/reference/base-pay/getStatus">
    Verify subscription before revoking
  </Card>

  <Card title="Custom Revoke" icon="code" href="/base-account/reference/base-pay/prepareRevoke">
    Advanced manual execution
  </Card>
</CardGroup>

<BaseBanner
  id="privacy-policy"
  dismissable={false}
  content={({ onDismiss }) => (
<div className="flex items-center">
  <div className="mr-2">
    We're updating the Base Privacy Policy, effective July 25, 2025, to reflect an expansion of Base services. Please review the updated policy here:{" "}
    <a
      href="https://docs.base.org/privacy-policy-2025"
      target="_blank"
      className="whitespace-nowrap"
    >
      Base Privacy Policy
    </a>. By continuing to use Base services, you confirm that you have read and understand the updated policy.
  </div>
  <Button onClick={onDismiss}>I Acknowledge</Button>
</div>
)}
/>
