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
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
| Field | Type | Description |
|---|---|---|
newExpiresAt | string (ISO 8601) | The updated session expiration timestamp |
extensionMinutes | number | Minutes added to the session |
tokenCost | number | SMART tokens deducted |
newBalance | number | Remaining 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
| HTTP | Error Code | Meaning | Handling |
|---|---|---|---|
| 402 | INSUFFICIENT_BALANCE | Not enough tokens for extension | Show balance, link to top-up |
| 410 | SESSION_EXPIRED | Session already ended | Redirect to marketplace |
| 429 | RATE_LIMITED | Too many extension requests | Retry 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:
| Factor | Description |
|---|---|
| Base rate | Tokens per minute set by the provider |
| Duration | Number of minutes being added |
| Ceiling | Cost 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
- Handle session termination when the session ends
- Receive webhook events for extension notifications
- Review the in-session purchase flow for item-based purchases