Skip to main content

Session Extension

Users can purchase additional session time before their current session expires. The extension flow involves checking the cost, confirming with the user, executing the extension, and updating the session timer with the new expiration.

Extension Flow

sequenceDiagram
participant User
participant App as Your Application
participant SDK as Provider SDK
participant API as GW Platform API

Note over User,API: Session warning at 5 minutes remaining
SDK->>App: onSessionWarning fires
App->>User: Show "Extend Session?" prompt
User->>App: Clicks "Extend"
App->>API: GET /extension-cost
API-->>App: { extensionMinutes: 30, tokenCost: 15 }
App->>User: "30 min for 15 tokens. Confirm?"
User->>App: Confirms extension
App->>API: POST /extend { extensionMinutes: 30 }
API->>API: Deduct tokens, update expiration
API-->>App: { newExpiresAt, extensionMinutes, newBalance }
App->>SDK: Update internal timer
SDK->>SDK: Reset countdown to new expiration
SDK->>App: onSessionExtend fires
App->>User: "Extended! 30 more minutes."

Checking Extension Cost

Before presenting the extension option, query the cost:

const response = await fetch(
`${apiBase}/api/sessions/${sessionId}/extension-cost`,
{ headers: { Authorization: `Bearer ${token}` } }
);

const { extensionMinutes, tokenCost } = await response.json();
// { extensionMinutes: 30, tokenCost: 15 }

The response tells you:

  • extensionMinutes - How many minutes the extension adds
  • tokenCost - How many SMART tokens it costs
tip

Display both the cost and the current balance so users can make an informed decision before committing.

Requesting an Extension

Once the user confirms, submit the extension request:

const response = await fetch(
`${apiBase}/api/sessions/${sessionId}/extend`,
{
method: 'POST',
headers: {
'Content-Type': 'application/json',
Authorization: `Bearer ${token}`,
},
body: JSON.stringify({ extensionMinutes: 30 }),
}
);

if (!response.ok) {
const error = await response.json();
// Handle error (see Error Handling below)
return;
}

const { newExpiresAt, extensionMinutes, tokenCost, newBalance } =
await response.json();

Response Fields

FieldTypeDescription
newExpiresAtstring (ISO 8601)The updated session expiration timestamp
extensionMinutesnumberMinutes added to the session
tokenCostnumberSMART tokens deducted
newBalancenumberRemaining token balance after deduction

Updating the Timer

After a successful extension, the session timer must reflect the new expiration. How you handle this depends on your integration approach.

With the SDK

The SDK handles timer updates automatically via the onSessionExtend hook:

const sdk = new MarketplaceSDK({
applicationId: 'your-app-id',
apiKey: 'gwsk_...',
environment: 'production',
hooks: {
onSessionExtend: async (context) => {
console.log('Extended by', context.extensionMinutes, 'minutes');
console.log('New expiration:', new Date(context.newExpiresAt * 1000));

// Update any app-specific state
appState.setSessionExpiration(context.newExpiresAt);

// Refresh app auth token if it expires before the new session end
if (needsTokenRefresh(context.newExpiresAt)) {
await refreshAppToken();
}

showNotification(
`Session extended! ${context.extensionMinutes} more minutes added.`
);
},
},
});

// Trigger extension via SDK
await sdk.extendSession(30);

Without the SDK (Manual)

If you manage the session timer yourself, update it after a successful extension:

async function extendSession(sessionId: string, token: string) {
const response = await fetch(`/api/sessions/${sessionId}/extend`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
Authorization: `Bearer ${token}`,
},
body: JSON.stringify({ extensionMinutes: 30 }),
});

if (!response.ok) {
throw new Error('Extension failed');
}

const { newExpiresAt } = await response.json();

// Update your session timer
const newExpiration = new Date(newExpiresAt);
sessionTimer.updateExpiration(newExpiration);

// Update stored session data
localStorage.setItem('session_expires_at', newExpiresAt);
}

Warning Threshold Integration

The SDK fires onSessionWarning when the session is about to expire (default: 5 minutes remaining). This is the ideal moment to offer an extension:

onSessionWarning: async (context) => {
// Auto-save user work
await autoSave();

// Show extension offer
const { tokenCost } = await fetchExtensionCost(context.sessionId);
const balance = await fetchBalance(context.sessionId);

showModal({
title: 'Session Expiring Soon',
message: `Your session expires in ${context.minutesRemaining} minutes.`,
buttons: [
{
label: `Extend (+30 min, ${tokenCost} tokens)`,
disabled: balance < tokenCost,
action: async () => {
await sdk.extendSession(30);
dismissModal();
},
},
{
label: 'Save & Exit',
action: async () => {
await saveAllWork();
sdk.endSession('manual');
},
},
{ label: 'Continue', action: () => dismissModal() },
],
});
},

Customizing the Warning Threshold

const sdk = new MarketplaceSDK({
applicationId: 'your-app-id',
apiKey: 'gwsk_...',
warningThresholdMinutes: 10, // Warn 10 minutes before expiration
});

Error Handling

HTTPError CodeMeaningHandling
402INSUFFICIENT_BALANCENot enough tokens for extensionShow balance, link to top-up
410SESSION_EXPIREDSession already endedRedirect to marketplace
429RATE_LIMITEDToo many extension requestsRetry with exponential back-off

Insufficient Balance Example

async function handleExtension(sessionId: string, token: string) {
const costResponse = await fetch(
`/api/sessions/${sessionId}/extension-cost`,
{ headers: { Authorization: `Bearer ${token}` } }
);
const { tokenCost } = await costResponse.json();

const balanceResponse = await fetch(
`/api/sessions/${sessionId}/balance`,
{ headers: { Authorization: `Bearer ${token}` } }
);
const { balance } = await balanceResponse.json();

if (balance < tokenCost) {
showModal({
title: 'Insufficient Balance',
message: `Extension costs ${tokenCost} tokens, but you only have ${balance}.`,
action: {
label: 'Add Tokens',
href: 'https://platform.generalwisdom.com/tokens',
},
});
return;
}

// Proceed with extension
const response = await fetch(`/api/sessions/${sessionId}/extend`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
Authorization: `Bearer ${token}`,
},
body: JSON.stringify({ extensionMinutes: 30 }),
});

if (!response.ok) {
const error = await response.json();
showNotification(`Extension failed: ${error.message}`);
return;
}

const { newExpiresAt, newBalance } = await response.json();
showNotification(`Session extended! New balance: ${newBalance} tokens`);
}

Extension Pricing

Extension costs are calculated per-minute by the platform based on your application's pricing configuration:

FactorDescription
Base rateTokens per minute set by the provider
DurationNumber of minutes being added
CeilingCost is always rounded up to the nearest whole token

For example, with a base rate of 0.5 tokens/minute and a 30-minute extension:

cost = ceil(30 * 0.5) = 15 SMART tokens

Next Steps