Skip to main content
GET
/
api
/
cron
/
remove-expired
Remove Expired Subscriptions
curl --request GET \
  --url https://api.example.com/api/cron/remove-expired
{
  "success": true,
  "processed": 123,
  "removed": 123,
  "failed": 123,
  "timestamp": "<string>"
}

Overview

This endpoint is designed to be called by a cron job scheduler (e.g., Vercel Cron, GitHub Actions, or external cron services) to automatically process expired subscriptions. It removes users from the Telegram channel, deletes their MetaCopier accounts, and sends notifications.
This endpoint is open (no authentication required in the current implementation) and should be protected by your cron service’s authentication or network-level security.

Authentication

Security Consideration:The endpoint currently does not require authentication. For production deployments, consider:
  • Restricting access by IP address (cron service IPs only)
  • Adding a secret token in query parameters or headers
  • Using Vercel Cron’s built-in authentication
  • Implementing rate limiting to prevent abuse

Request

The endpoint accepts both GET and POST methods for flexibility with different cron services.
GET /api/cron/remove-expired
No request body or parameters required.

Process Flow

When triggered, the endpoint executes the following workflow:
  1. Query Expired Subscriptions
    • Finds all subscriptions where expiresAt < NOW() and isRemoved = false
    • Includes related mt5Setup data for MetaCopier cleanup
  2. MetaCopier Cleanup (if applicable)
    • Checks if user has a MetaCopier account setup
    • Calls removeUserMt5Account() to delete the account
    • Sends admin notification on success or failure
    • Continues with channel removal even if MetaCopier cleanup fails
  3. Channel Removal
    • Bans/removes user from Telegram channel using banChatMember()
    • Updates subscription record: isRemoved = true, removedAt = NOW()
  4. User Notification
    • Sends expiration message to user with renewal instructions
    • Includes pricing information for all plan tiers
  5. Admin Notifications
    • Success: Confirms MetaCopier and channel removal
    • Failure: Alerts admin with account details for manual intervention

Response

success
boolean
Indicates whether the cron job completed without critical errors.
processed
number
Total number of expired subscriptions found and processed.
removed
number
Number of users successfully removed from the channel.
failed
number
Number of removals that failed (will be retried on next cron run).
timestamp
string
ISO 8601 timestamp of when the job executed.

Examples

curl -X GET https://your-domain.com/api/cron/remove-expired

Response Examples

{
  "success": true,
  "processed": 5,
  "removed": 4,
  "failed": 1,
  "timestamp": "2026-03-03T14:30:00.000Z"
}

User Notification Message

When a subscription expires, users receive the following notification:
⏰ Your subscription has expired.

━━━━━━━━━━━━━━━━━━━

Your access to Pear VIP signals channel has been removed.

━━━━━━━━━━━━━━━━━━━

Want to renew?
Just tap the button below to make a new payment!

💎 Basic: ₦5,000 (7 days)
📊 Bi-Weekly: ₦10,000 (14 days)
📅 Monthly: ₦15,000 (30 days)
👑 Premium: ₦22,000 (14 days + Copier)

━━━━━━━━━━━━━━━━━━━

Or type /pay to get started.

Admin Notifications

Successful MetaCopier Removal

✅ MetaCopier Account Removed

━━━━━━━━━━━━━━━━━━━

User: 123456789 (@username)
MetaCopier Account: MC12345
Reason: Subscription expired

━━━━━━━━━━━━━━━━━━━

User has been removed from channel and MetaCopier.

Failed MetaCopier Removal (Manual Action Required)

⚠️ MetaCopier Removal Failed - Manual Action Needed!

━━━━━━━━━━━━━━━━━━━

User: 123456789 (@username)
MetaCopier Account: MC12345
MetaCopier Copier: COPIER789
Error: API connection timeout

━━━━━━━━━━━━━━━━━━━

Action Required:
• User has been removed from channel
• But MetaCopier account could NOT be removed automatically
• Please remove manually from MetaCopier dashboard

Login: 12345678
Server: ICMarkets-Demo01

MetaCopier Integration

Premium Plan Handling:The endpoint includes special handling for Premium plan subscribers who have MetaCopier accounts:
  • Checks for mt5Setup.metacopierAccountId in the subscription
  • Calls removeUserMt5Account() with account ID and copier ID
  • Logs all removal attempts and results
  • Sends detailed admin notifications for failed removals
  • Continues with Telegram channel removal even if MetaCopier cleanup fails
Source: src/app/api/cron/remove-expired/route.ts:29-97

Error Handling and Retry Logic

The endpoint implements robust error handling:
  • Individual Subscription Failures: If a single subscription removal fails, it won’t be marked as isRemoved, allowing retry on the next cron run
  • MetaCopier Failures: Channel removal proceeds even if MetaCopier cleanup fails, with admin notification for manual intervention
  • Telegram API Failures: Logged with user details, subscription remains in retry queue
  • Database Errors: Returned as 500 error with error message
Scheduling Best Practices:
  • Every hour (0 * * * *): Standard recommendation for most deployments
  • Every 30 minutes (*/30 * * * *): For high-volume services requiring faster response
  • Every 4 hours (0 */4 * * *): For low-volume services to reduce API calls
Consider your subscription volume and user expectations when choosing frequency.

Monitoring and Observability

The endpoint provides detailed console logging for monitoring:
// Success logs
console.log(`Sending broadcast to ${subscriptions.length} users`)
console.log(`Removed user ${subscription.telegramUserId} (subscription ${subscription.id})`)
console.log(`Successfully removed MetaCopier account for user ${subscription.telegramUserId}`)

// Error logs
console.error(`Failed to ban user ${subscription.telegramUserId}`)
console.error(`Failed to remove MetaCopier account: ${result.error}`)
console.error(`Error removing subscription ${subscription.id}:`, error)
These logs can be integrated with services like:
  • Vercel Logs
  • Datadog
  • Sentry
  • LogRocket
  • CloudWatch

Database Schema Requirements

The endpoint expects the following Prisma schema structure:
model Subscription {
  id              String    @id @default(cuid())
  telegramUserId  BigInt
  telegramUsername String?
  planType        String
  expiresAt       DateTime
  isRemoved       Boolean   @default(false)
  removedAt       DateTime?
  mt5Setup        Mt5Setup?
}

model Mt5Setup {
  id                    String       @id @default(cuid())
  subscriptionId        String       @unique
  subscription          Subscription @relation(fields: [subscriptionId], references: [id])
  metacopierAccountId   String?
  metacopierCopierId    String?
  loginAccountNumber    String
  loginServer           String
}

Testing

For testing in development, you can manually trigger the endpoint:
# Test locally
curl http://localhost:3000/api/cron/remove-expired

# Test staging
curl https://staging.your-domain.com/api/cron/remove-expired

# Test production (be careful!)
curl https://your-domain.com/api/cron/remove-expired
Testing Precautions:Be cautious when testing in production as this endpoint will actually remove users and send notifications. Consider:
  • Testing in a staging environment first
  • Using a test Telegram channel
  • Temporarily modifying the query to only process specific test subscriptions
  • Checking the processed count before actual removal

Implementation Reference

Full implementation details can be found in:
  • Source File: src/app/api/cron/remove-expired/route.ts
  • Helper Functions: src/lib/telegram.ts (banChatMember, sendMessage)
  • MetaCopier API: src/lib/metacopier.ts (removeUserMt5Account)
  • Configuration: src/lib/config.ts (ADMIN_ID)

Build docs developers (and LLMs) love