Skip to main content
DELETE
/
subscription
Cancel subscription
curl --request DELETE \
  --url https://utilsio.dev/api/v1/subscription \
  --header 'Content-Type: application/json' \
  --header 'X-utilsio-Signature: <api-key>' \
  --header 'X-utilsio-Timestamp: <x-utilsio-timestamp>' \
  --data '
{
  "userId": "user_123",
  "deviceId": "device_abc",
  "appId": "app_xyz",
  "subscriptionIds": [
    "sub_abc123",
    "sub_def456"
  ]
}
'
{
  "success": true
}

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

HeaderRequiredDescription
X-utilsio-SignatureConditionalHMAC-SHA256 signature. Required unless using Safari fallback flow (see below)
X-utilsio-TimestampConditionalUnix timestamp in seconds. Required unless using Safari fallback flow

Body

FieldTypeRequiredDescription
userIdstringYesUser ID who owns the subscription
deviceIdstringConditionalDevice ID. Optional if using Safari fallback - will be read from cookies
appIdstringYesYour application ID
subscriptionIdsstring[]YesArray of subscription IDs to cancel
signatureCallbackUrlstringConditionalRequired 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:
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):
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 });
}
Security: For DELETE requests, you MUST include sorted subscriptionIds (comma-separated) as additionalData in the signature to prevent tampering.

Example Request

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)

{
  "success": true
}

Error Responses

StatusErrorCause
400userId, appId, and subscriptionIds are requiredMissing required fields
400Device ID not found. Please ensure cookies are enabled.Safari flow failed to read deviceId from cookies
400signatureCallbackUrl is required when signature is not providedSafari flow missing callback URL
400User wallet not configuredUser hasn’t set up their wallet
401Invalid signatureSignature verification failed
403Some subscriptions not found or unauthorizedUser doesn’t own all specified subscriptions
404No valid subscriptions found to deleteNone of the provided IDs exist
500Failed to obtain signature from appSafari flow: callback to app failed
500Failed to delete subscription streamOn-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:
'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

Authorizations

X-utilsio-Signature
string
header
required

Headers

X-utilsio-Signature
string
required

HMAC signature for request authentication

X-utilsio-Timestamp
string
required

Unix timestamp for request signing

Body

application/json
userId
string
required

The user ID who owns the subscription

deviceId
string
required

The device ID that initiated the subscription

appId
string
required

The application ID of the subscription

subscriptionIds
string[]
required

Array of subscription IDs to cancel

Unique identifier for a subscription

Response

Subscriptions cancelled successfully.

success
boolean

Whether the cancellation was successful

error
string

Error message if the cancellation failed

Last modified on February 6, 2026