> ## Documentation Index
> Fetch the complete documentation index at: https://utilsio.dev/docs/llms.txt
> Use this file to discover all available pages before exploring further.

# Cancel Subscription

> Cancels one or more active subscriptions. Only subscriptions owned by the requesting user and belonging to the specified app can be cancelled.

## Overview

The DELETE `/subscription` endpoint cancels one or more active subscriptions for a user. The cancellation is processed on-chain via the smart contract wallet system.

## How It Works

1. **Signature Verification**: Request must include valid HMAC-SHA256 signature proving it comes from your authorized app
2. **Ownership Check**: Only subscriptions owned by the requesting user can be cancelled
3. **On-Chain Execution**: Subscription streams are closed via smart contract interaction
4. **Gas Fee Handling**: Gas fees are automatically estimated and added (with 10% safety markup)

## Request Parameters

### Headers

| Header                | Required    | Description                                                                   |
| --------------------- | ----------- | ----------------------------------------------------------------------------- |
| `X-utilsio-Signature` | Conditional | HMAC-SHA256 signature. Required unless using Safari fallback flow (see below) |
| `X-utilsio-Timestamp` | Conditional | Unix timestamp in seconds. Required unless using Safari fallback flow         |

### Body

| Field                  | Type      | Required    | Description                                                                                                          |
| ---------------------- | --------- | ----------- | -------------------------------------------------------------------------------------------------------------------- |
| `userId`               | string    | Yes         | User ID who owns the subscription                                                                                    |
| `deviceId`             | string    | Conditional | Device ID. Optional if using Safari fallback - will be read from cookies                                             |
| `appId`                | string    | Yes         | Your application ID                                                                                                  |
| `subscriptionIds`      | string\[] | Yes         | Array of subscription IDs to cancel                                                                                  |
| `signatureCallbackUrl` | string    | Conditional | Required for Safari fallback when signature headers are not provided. URL to your `/api/signature-callback` endpoint |

## Signature Generation

### Normal Flow (Client-Side)

When `deviceId` is available (Chrome, Firefox, etc.), generate the signature client-side:

```typescript theme={null}
import { deriveAppHashHex, signRequest, nowUnixSeconds } from '@utilsio/react/server';

// Derive key (do this once at startup)
const appHashHex = deriveAppHashHex({
  appSecret: process.env.UTILSIO_APP_SECRET!,
  salt: process.env.UTILSIO_APP_SALT!,
});

// Sign the request
const timestamp = nowUnixSeconds();
const additionalData = subscriptionIds.sort().join(','); // Include subscription IDs in signature

const signature = signRequest({
  appHashHex,
  deviceId,
  appId: process.env.NEXT_PUBLIC_UTILSIO_APP_ID!,
  timestamp,
  additionalData, // IMPORTANT: Include this for DELETE requests
});
```

### Safari Fallback Flow (Server-Side)

In Safari, third-party cookies are blocked so `deviceId` is not available client-side. Instead:

1. Client makes DELETE request without `deviceId` or signature headers
2. Include `signatureCallbackUrl` in request body
3. Server reads `deviceId` from first-party cookies
4. Server makes callback to your `/api/signature-callback` endpoint
5. Your endpoint generates and returns signature
6. Server verifies signature and processes deletion

**Your callback endpoint** (`/api/signature-callback`):

```typescript theme={null}
export async function POST(req: NextRequest) {
  const { deviceId, appId, additionalData, timestamp } = await req.json();

  const signature = signRequest({
    appHashHex,
    deviceId,
    appId,
    timestamp,
    additionalData, // subscriptionIds sorted and joined
  });

  return NextResponse.json({ signature, timestamp });
}
```

<Warning>
  **Security:** For DELETE requests, you MUST include sorted `subscriptionIds` (comma-separated) as `additionalData` in the signature to prevent tampering.
</Warning>

## Example Request

```bash theme={null}
curl -X DELETE 'https://utilsio.dev/api/v1/subscription' \
  -H 'Content-Type: application/json' \
  -H 'X-utilsio-Signature: abc123...' \
  -H 'X-utilsio-Timestamp: 1705334400' \
  -d '{
    "userId": "user_123",
    "deviceId": "device_abc",
    "appId": "app_xyz",
    "subscriptionIds": ["sub_abc123"]
  }'
```

## Response

### Success (200)

```json theme={null}
{
  "success": true
}
```

### Error Responses

| Status | Error                                                             | Cause                                            |
| ------ | ----------------------------------------------------------------- | ------------------------------------------------ |
| 400    | `userId, appId, and subscriptionIds are required`                 | Missing required fields                          |
| 400    | `Device ID not found. Please ensure cookies are enabled.`         | Safari flow failed to read deviceId from cookies |
| 400    | `signatureCallbackUrl is required when signature is not provided` | Safari flow missing callback URL                 |
| 400    | `User wallet not configured`                                      | User hasn't set up their wallet                  |
| 401    | `Invalid signature`                                               | Signature verification failed                    |
| 403    | `Some subscriptions not found or unauthorized`                    | User doesn't own all specified subscriptions     |
| 404    | `No valid subscriptions found to delete`                          | None of the provided IDs exist                   |
| 500    | `Failed to obtain signature from app`                             | Safari flow: callback to app failed              |
| 500    | `Failed to delete subscription stream`                            | On-chain transaction failed                      |

## Gas Fees

Gas fees are automatically handled:

* Estimated based on current network conditions
* 10% safety markup added to prevent failures
* Deducted from user's wallet balance
* No manual gas fee calculation required

## Using the SDK

The React SDK handles all complexity for you:

```typescript theme={null}
'use client';
import { useUtilsio } from '@utilsio/react/client';

export default function ManageSubscription() {
  const { user, deviceId, currentSubscription, cancelSubscription } = useUtilsio();

  const handleCancel = async () => {
    if (!currentSubscription) return;
    if (!confirm('Are you sure you want to cancel your subscription?')) return;

    try {
      await cancelSubscription([currentSubscription.id]);
      // refresh() is called automatically, component re-renders
    } catch (err) {
      console.error('Cancellation failed:', err);
    }
  };

  return currentSubscription ? (
    <button onClick={handleCancel}>
      Cancel Subscription
    </button>
  ) : null;
}
```

## Notes

* Cancellations are immediate and irreversible
* Users can re-subscribe at any time
* On-chain confirmation may take a few seconds
* Consider showing a loading state during cancellation
* The SDK's `cancelSubscription()` automatically refreshes subscription state after success


## OpenAPI

````yaml DELETE /subscription
openapi: 3.1.0
info:
  title: utilsio API
  description: Integrating crypto subscriptions to SaaS apps
  version: 1.0.0
  license:
    name: MIT
servers:
  - url: https://utilsio.dev/api/v1
    description: Production API
security:
  - signatureAuth: []
paths:
  /subscription:
    delete:
      summary: Cancel subscription
      description: >-
        Cancels one or more active subscriptions. Only subscriptions owned by
        the requesting user and belonging to the specified app can be cancelled.
      operationId: cancelSubscription
      parameters:
        - name: X-utilsio-Signature
          in: header
          required: true
          description: HMAC signature for request authentication
          schema:
            type: string
        - name: X-utilsio-Timestamp
          in: header
          required: true
          description: Unix timestamp for request signing
          schema:
            type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/DeleteSubscriptionRequest'
            examples:
              request:
                value:
                  userId: user_123
                  deviceId: device_abc
                  appId: app_xyz
                  subscriptionIds:
                    - sub_abc123
                    - sub_def456
      responses:
        '200':
          description: Subscriptions cancelled successfully.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/DeleteSubscriptionResponse'
              examples:
                success:
                  value:
                    success: true
        '400':
          description: Missing required fields or user wallet not configured
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/DeleteSubscriptionResponse'
              examples:
                missingFields:
                  value:
                    success: false
                    error: userId, deviceId, appId, and subscriptionIds are required
                walletNotConfigured:
                  value:
                    success: false
                    error: User wallet not configured
        '401':
          description: Authentication headers missing or invalid
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/DeleteSubscriptionResponse'
              examples:
                missingHeaders:
                  value:
                    success: false
                    error: >-
                      X-utilsio-Signature and X-utilsio-Timestamp headers are
                      required
        '403':
          description: Some subscriptions not found or unauthorized
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/DeleteSubscriptionResponse'
              examples:
                partialNotFound:
                  value:
                    success: false
                    error: Some subscriptions not found or unauthorized
        '404':
          description: No valid subscriptions found to delete
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/DeleteSubscriptionResponse'
              examples:
                notFound:
                  value:
                    success: false
                    error: No valid subscriptions found to delete
        '500':
          description: Failed to delete subscription stream
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/DeleteSubscriptionResponse'
              examples:
                streamDeleteError:
                  value:
                    success: false
                    error: Failed to delete subscription stream
                serverError:
                  value:
                    success: false
                    error: Internal Server Error
components:
  schemas:
    DeleteSubscriptionRequest:
      type: object
      required:
        - userId
        - deviceId
        - appId
        - subscriptionIds
      properties:
        userId:
          type: string
          description: The user ID who owns the subscription
        deviceId:
          type: string
          description: The device ID that initiated the subscription
        appId:
          type: string
          description: The application ID of the subscription
        subscriptionIds:
          type: array
          description: Array of subscription IDs to cancel
          items:
            type: string
            description: Unique identifier for a subscription
    DeleteSubscriptionResponse:
      type: object
      properties:
        success:
          type: boolean
          description: Whether the cancellation was successful
        error:
          type: string
          description: Error message if the cancellation failed
  securitySchemes:
    signatureAuth:
      type: apiKey
      in: header
      name: X-utilsio-Signature

````