Handling Boxing Events
Handling Boxing Events
Section titled “Handling Boxing Events”This guide covers how to respond when identities are boxed, including grace period management and user notifications.
When Identities Get Boxed
Section titled “When Identities Get Boxed”When a creator boxes their identity, your platform receives webhooks and may have existing avatars affected. Here’s how to handle this:
Webhook Setup
Section titled “Webhook Setup”First, subscribe to relevant events:
await lmif.webhooks.create({ url: 'https://yourplatform.com/webhooks/lmif', events: [ 'box.created', // New identity boxed 'box.updated', // Policy changed 'violation.detected', // Match found on your platform 'grace_period.started', // 30-day countdown begins 'grace_period.reminder', // Day 7, 21, 28 reminders 'grace_period.ending', // 2 days left 'grace_period.expired', // Time to enforce ],});Handling box.created
Section titled “Handling box.created”When a new identity is boxed, scan your existing avatars:
async function handleBoxCreated(event: BoxCreatedEvent) { const { boxId, identityName, policy } = event.data;
// Search your avatars for potential matches const potentialMatches = await db.avatars.search({ name: { $similar: identityName }, });
if (potentialMatches.length > 0) { // Re-check each potential match const checks = await lmif.identity.checkBatch( potentialMatches.map((avatar) => ({ id: avatar.id, name: avatar.name, imageUrl: avatar.imageUrl, })) );
// Handle matches for (const check of checks) { if (check.isBoxed && check.matchedIdentity?.boxId === boxId) { await handleNewViolation(check.id, check); } } }}Handling grace_period.started
Section titled “Handling grace_period.started”When a grace period starts, notify affected users:
async function handleGracePeriodStarted(event: GracePeriodStartedEvent) { const { gracePeriodId, boxId, identityName, expiresAt, affectedAvatars } = event.data;
for (const avatar of affectedAvatars) { // Notify avatar creator await notifications.send({ userId: avatar.creatorId, type: 'grace_period_started', title: 'Action Required: Avatar Compliance', message: `Your avatar "${avatar.name}" uses the likeness of ${identityName}, who has protected their identity. You have 30 days to comply.`, data: { gracePeriodId, avatarId: avatar.avatarId, expiresAt, options: ['license', 'modify', 'remove'], }, });
// Update avatar status in database await db.avatars.update(avatar.avatarId, { status: 'grace_period', gracePeriod: { id: gracePeriodId, expiresAt, identityName, }, });
// Optionally notify users of the avatar await notifyAvatarUsers(avatar.avatarId, { type: 'avatar_warning', message: `This avatar may become unavailable in 30 days. We'll suggest alternatives.`, }); }}Handling grace_period.reminder
Section titled “Handling grace_period.reminder”Send reminders at key intervals:
async function handleGracePeriodReminder(event: GracePeriodReminderEvent) { const { gracePeriodId, reminderDay, daysRemaining, affectedAvatars } = event.data;
const urgency = { 7: 'normal', 21: 'elevated', 28: 'urgent', }[reminderDay];
for (const avatar of affectedAvatars) { // Skip already-resolved avatars if (avatar.status === 'resolved') continue;
await notifications.send({ userId: avatar.creatorId, type: 'grace_period_reminder', urgency, title: `${daysRemaining} days remaining`, message: `Your avatar "${avatar.name}" needs attention. ${daysRemaining} days left to comply.`, actions: [ { label: 'Get License', url: `/license/${avatar.avatarId}` }, { label: 'Modify Avatar', url: `/edit/${avatar.avatarId}` }, { label: 'Remove Avatar', action: 'remove' }, ], }); }}Handling grace_period.ending
Section titled “Handling grace_period.ending”Final warning before enforcement:
async function handleGracePeriodEnding(event: GracePeriodEndingEvent) { const { gracePeriodId, expiresAt, unresolvedAvatars } = event.data;
for (const avatar of unresolvedAvatars) { // Send urgent notification await notifications.send({ userId: avatar.creatorId, type: 'grace_period_final_warning', urgency: 'critical', title: 'URGENT: 2 Days Remaining', message: `Your avatar "${avatar.name}" will be deactivated in 2 days unless you take action.`, });
// Send email as backup await email.send({ to: await getUserEmail(avatar.creatorId), subject: '[URGENT] Your avatar will be deactivated in 2 days', template: 'grace_period_final_warning', data: { avatar, expiresAt }, });
// Also notify users await notifyAvatarUsers(avatar.avatarId, { type: 'avatar_expiring_soon', message: 'This avatar will become unavailable in 2 days.', suggestAlternatives: true, }); }}Handling grace_period.expired
Section titled “Handling grace_period.expired”Time to enforce:
async function handleGracePeriodExpired(event: GracePeriodExpiredEvent) { const { gracePeriodId, avatarsToDeactivate } = event.data;
for (const avatar of avatarsToDeactivate) { // Deactivate the avatar await db.avatars.update(avatar.avatarId, { status: 'deactivated', deactivatedAt: new Date(), deactivationReason: 'grace_period_expired', });
// Notify creator await notifications.send({ userId: avatar.creatorId, type: 'avatar_deactivated', title: 'Avatar Deactivated', message: `Your avatar "${avatar.name}" has been deactivated due to identity protection.`, });
// Migrate users to alternatives await migrateUsersToAlternatives(avatar.avatarId); }}User Migration
Section titled “User Migration”When an avatar is deactivated, provide a smooth transition:
async function migrateUsersToAlternatives(avatarId: string) { const users = await db.userAvatars.findByAvatar(avatarId); const avatar = await db.avatars.get(avatarId);
// Find similar avatars const alternatives = await findAlternativeAvatars(avatar);
for (const user of users) { // Preserve conversation history await archiveConversations(user.id, avatarId);
// Suggest alternatives await notifications.send({ userId: user.id, type: 'avatar_alternatives', title: 'Your avatar is no longer available', message: `${avatar.name} has been deactivated. We've saved your conversations and found similar avatars.`, data: { archivedConversations: `/conversations/archived/${avatarId}`, alternatives: alternatives.map((a) => ({ id: a.id, name: a.name, similarity: a.similarityScore, })), }, }); }}Resolution Options
Section titled “Resolution Options”Provide clear paths for avatar creators to resolve violations:
Option 1: Obtain License
Section titled “Option 1: Obtain License”async function requestLicenseForAvatar(avatarId: string, tier: string) { const avatar = await db.avatars.get(avatarId);
const request = await lmif.licenses.request({ boxId: avatar.gracePeriod.boxId, tier, useCase: `Avatar on ${PLATFORM_NAME}`, estimatedUsers: avatar.userCount, acceptGuidelines: true, });
await db.avatars.update(avatarId, { licenseRequest: { id: request.id, status: 'pending', requestedAt: new Date(), }, });
return request;}Option 2: Modify Avatar
Section titled “Option 2: Modify Avatar”async function modifyAvatarToComply(avatarId: string, changes: AvatarChanges) { // Update avatar to remove likeness await db.avatars.update(avatarId, { name: changes.newName, imageUrl: changes.newImageUrl, description: changes.newDescription, });
// Re-check with LMIF const check = await lmif.identity.check({ name: changes.newName, imageUrl: changes.newImageUrl, });
if (!check.isBoxed) { // Resolve the violation await lmif.gracePeriods.resolve(avatar.gracePeriod.id, { resolution: 'modified', avatarId, });
await db.avatars.update(avatarId, { status: 'active', gracePeriod: null, }); }
return check;}Option 3: Remove Avatar
Section titled “Option 3: Remove Avatar”async function removeAvatarToComply(avatarId: string) { const avatar = await db.avatars.get(avatarId);
// Soft delete await db.avatars.update(avatarId, { status: 'removed', removedAt: new Date(), removedReason: 'identity_protection', });
// Notify LMIF await lmif.gracePeriods.resolve(avatar.gracePeriod.id, { resolution: 'removed', avatarId, });
// Handle users await migrateUsersToAlternatives(avatarId);}Complete Webhook Handler
Section titled “Complete Webhook Handler”Here’s a complete webhook handler implementation:
import express from 'express';import { LMIFClient } from '@lookmaimfamous/lmif';
const app = express();const lmif = new LMIFClient({ apiKey: process.env.LMIF_API_KEY });
app.post('/webhooks/lmif', express.raw({ type: 'application/json' }), async (req, res) => { // Verify signature const signature = req.headers['x-lmif-signature'] as string; if (!lmif.webhooks.verify(req.body, signature, process.env.LMIF_WEBHOOK_SECRET)) { return res.status(401).send('Invalid signature'); }
// Acknowledge immediately res.status(200).send('OK');
// Process asynchronously const event = JSON.parse(req.body.toString());
try { switch (event.type) { case 'box.created': await handleBoxCreated(event); break; case 'box.updated': await handleBoxUpdated(event); break; case 'violation.detected': await handleViolationDetected(event); break; case 'grace_period.started': await handleGracePeriodStarted(event); break; case 'grace_period.reminder': await handleGracePeriodReminder(event); break; case 'grace_period.ending': await handleGracePeriodEnding(event); break; case 'grace_period.expired': await handleGracePeriodExpired(event); break; } } catch (error) { console.error(`Failed to handle ${event.type}:`, error); // Consider alerting on failure }});