LogoNEXTDEVKIT Docs

Cron Jobs Setup

Configure automated credit grants and expiration with various cron job platforms

📋 Overview

The credits system requires two automated cron jobs to function properly:

  1. Grant Credits Job - Distributes monthly credits to subscription users
  2. Expire Credits Job - Processes expired credit batches

Both jobs must be configured to call your API endpoints with proper authentication.

🔐 Authentication

All cron job requests must include the CRON_SECRET in the Authorization header:

Authorization: Bearer your-cron-secret-here

Generating CRON_SECRET

Generate a secure random string (16+ characters):

# Using OpenSSL
openssl rand -hex 16

# Using Node.js
node -e "console.log(require('crypto').randomBytes(16).toString('hex'))"

# Or use any password generator

Add to your .env file:

CRON_SECRET=a1b2c3d4e5f6g7h8i9j0k1l2m3n4o5p6

🔄 Cron Job Endpoints

1. Grant Credits Endpoint

URL: https://yourdomain.com/api/jobs/credits/grant

Purpose: Grants monthly subscription credits to eligible users

Schedule: Daily at 00:00 UTC (midnight UTC)

HTTP Method: GET

Headers:

Authorization: Bearer your-cron-secret

Response:

{
  "success": true,
  "jobId": "550e8400-e29b-41d4-a716-446655440000",
  "date": "2024-01-15",
  "duration": 1250,
  "results": {
    "processed": 150,
    "successful": 148,
    "skipped": 2,
    "failed": 0
  }
}

2. Expire Credits Endpoint

URL: https://yourdomain.com/api/jobs/credits/expire

Purpose: Processes expired credit batches and updates balances

Schedule: Daily at 01:00 UTC (1 AM UTC)

HTTP Method: GET

Headers:

Authorization: Bearer your-cron-secret

Response:

{
  "success": true,
  "jobId": "660e8400-e29b-41d4-a716-446655440001",
  "duration": 850,
  "results": {
    "processedBatches": 45,
    "processedUsers": 38,
    "totalExpiredAmount": 1250
  }
}

🚀 Platform Setup Guides

Option 1: Vercel Cron Jobs

Best for: Applications deployed on Vercel

Setup Steps

  1. Create vercel.json in your project root:
{
  "crons": [
    {
      "path": "/api/jobs/credits/grant",
      "schedule": "0 0 * * *"
    },
    {
      "path": "/api/jobs/credits/expire",
      "schedule": "0 1 * * *"
    }
  ]
}
  1. Add environment variable in Vercel:

Go to your project → Settings → Environment Variables:

  • Key: CRON_SECRET
  • Value: your-generated-secret
  1. Deploy your changes:
git add vercel.json
git commit -m "Add cron jobs configuration"
git push
  1. Verify in Vercel Dashboard:

Go to your project → Deployments → Cron Jobs to see active jobs.

Cron Schedule Format:

  • 0 0 * * * = Every day at 00:00 UTC
  • 0 1 * * * = Every day at 01:00 UTC

Important Notes:

  • ✅ Available on Vercel Pro plan and above
  • ✅ Automatic authentication with Vercel
  • ✅ Built-in monitoring and logs
  • ⚠️ Maximum execution time: 10 seconds (Hobby), 60 seconds (Pro), 300 seconds (Enterprise)

Option 2: Cloudflare Scheduled Workers

Best for: Applications deployed on Cloudflare Pages/Workers

Setup Steps

  1. Create a new Worker in Cloudflare Dashboard:

Navigate to Workers & Pages → Create Worker

  1. Configure the Worker code:
// grant-credits-worker.js
export default {
  async scheduled(event, env, ctx) {
    const response = await fetch('https://yourdomain.com/api/jobs/credits/grant', {
      method: 'GET',
      headers: {
        'Authorization': `Bearer ${env.CRON_SECRET}`,
      },
    });
    
    const data = await response.json();
    console.log('Grant credits job completed:', data);
  },
};
  1. Add Cron Trigger:

In Worker settings → Triggers → Cron Triggers:

  • Schedule: 0 0 * * *
  1. Set environment variable:

In Worker settings → Variables:

  • Variable name: CRON_SECRET
  • Value: your-generated-secret
  1. Repeat for expire credits job:

Create another Worker with schedule 0 1 * * * pointing to the expire endpoint.

Important Notes:

  • ✅ Free tier available with 100,000 requests/day
  • ✅ Global edge network
  • ✅ Reliable execution
  • ⚠️ Requires separate Worker for each cron job

Option 3: cron-job.org

Best for: Any deployment platform, simple setup

Setup Steps

  1. Sign up at cron-job.org

Free account allows up to 50 cron jobs.

  1. Create first cron job (Grant Credits):

Click "Create Cronjob" and configure:

General Settings:

  • Title: Grant Monthly Credits
  • Address: https://yourdomain.com/api/jobs/credits/grant
  • Request method: GET

Schedule:

  • Execution time: 00:00 (midnight)
  • Timezone: UTC
  • Days: Every day

Advanced Settings:

  • HTTP Headers:
    Authorization: Bearer your-cron-secret

Notifications:

  • Enable email notifications for failures (recommended)
  1. Create second cron job (Expire Credits):

Repeat with:

  • Title: Expire Credits
  • Address: https://yourdomain.com/api/jobs/credits/expire
  • Execution time: 01:00
  1. Save and activate:

Click "Create" and ensure the status shows as "Enabled".

Important Notes:

  • ✅ Free tier available
  • ✅ Email notifications for failures
  • ✅ Execution history and logs
  • ✅ Works with any platform
  • ⚠️ Less reliable than platform-native solutions
  • ⚠️ Requires manual setup for each environment

Option 4: Upstash QStash

Best for: Modern serverless applications, high reliability

Setup Steps

  1. Sign up at Upstash

Navigate to QStash section.

  1. Get your QStash token:

Copy your QStash Publishing Token from the dashboard.

  1. Create scheduled requests:

Using the QStash API or Dashboard:

Grant Credits Schedule:

curl -X POST "https://qstash.upstash.io/v2/schedules" \
  -H "Authorization: Bearer YOUR_QSTASH_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "destination": "https://yourdomain.com/api/jobs/credits/grant",
    "cron": "0 0 * * *",
    "headers": {
      "Authorization": "Bearer your-cron-secret"
    }
  }'

Expire Credits Schedule:

curl -X POST "https://qstash.upstash.io/v2/schedules" \
  -H "Authorization: Bearer YOUR_QSTASH_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "destination": "https://yourdomain.com/api/jobs/credits/expire",
    "cron": "0 1 * * *",
    "headers": {
      "Authorization": "Bearer your-cron-secret"
    }
  }'
  1. Verify in Upstash Dashboard:

Go to QStash → Schedules to see your configured jobs.

Important Notes:

  • ✅ Free tier: 500 messages/day
  • ✅ Built-in retries and dead letter queues
  • ✅ Excellent reliability
  • ✅ Request/response logs
  • ✅ Works with any platform
  • ⚠️ Requires API setup

Best for: Development/testing only

Setup Steps

  1. Create .github/workflows/cron-jobs.yml:
name: Credits Cron Jobs

on:
  schedule:
    # Grant credits daily at 00:00 UTC
    - cron: '0 0 * * *'
    # Expire credits daily at 01:00 UTC
    - cron: '0 1 * * *'
  workflow_dispatch: # Allow manual trigger

jobs:
  grant-credits:
    if: github.event.schedule == '0 0 * * *'
    runs-on: ubuntu-latest
    steps:
      - name: Grant Monthly Credits
        run: |
          curl -X GET "https://yourdomain.com/api/jobs/credits/grant" \
            -H "Authorization: Bearer ${{ secrets.CRON_SECRET }}"

  expire-credits:
    if: github.event.schedule == '0 1 * * *'
    runs-on: ubuntu-latest
    steps:
      - name: Expire Credits
        run: |
          curl -X GET "https://yourdomain.com/api/jobs/credits/expire" \
            -H "Authorization: Bearer ${{ secrets.CRON_SECRET }}"
  1. Add secret to GitHub:

Repository → Settings → Secrets → New repository secret:

  • Name: CRON_SECRET
  • Value: your-generated-secret

Why Not Recommended:

  • ⚠️ Less reliable (can be delayed or skipped)
  • ⚠️ Requires public repository or GitHub Pro
  • ⚠️ Not designed for critical operations
  • ⚠️ Rate limits may apply

🔍 Monitoring & Debugging

Check Cron Job Execution

Each job returns a unique jobId for tracking. Monitor your application logs:

# Vercel
vercel logs --follow

# Cloudflare
wrangler tail

# Check application logs for:
[Credits Grant Job abc123] Starting daily grant process
[Credits Grant Job abc123] Completed in 1250ms. Processed: 150, Successful: 148

Common Issues

1. Authentication Failed

Error: { "error": "Unauthorized" }

Solution:

  • Verify CRON_SECRET environment variable is set
  • Check the Authorization header format: Bearer your-secret
  • Ensure no extra spaces or newlines in the secret

2. Job Timeout

Error: Job times out or returns 504

Solutions:

  • Increase execution timeout in your platform settings
  • Optimize database queries
  • Process users in smaller batches
  • Consider breaking into multiple jobs

3. Duplicate Grants

Symptom: Users receive credits multiple times

Prevention:

  • The system automatically prevents duplicates using referenceId
  • Check grantPeriod uniqueness in database
  • Ensure only one cron job is configured per endpoint

4. No Credits Granted

Check:

// Verify subscription configuration
console.log(appConfig.credits.subscription);

// Check active subscriptions
const purchases = await db
  .select()
  .from(purchase)
  .where(eq(purchase.status, 'active'));
console.log(`Active subscriptions: ${purchases.length}`);

Manual Testing

Test the endpoints manually:

# Test grant endpoint
curl -X GET "https://yourdomain.com/api/jobs/credits/grant" \
  -H "Authorization: Bearer your-cron-secret"

# Test expire endpoint
curl -X GET "https://yourdomain.com/api/jobs/credits/expire" \
  -H "Authorization: Bearer your-cron-secret"

📊 Best Practices

1. Timing

Recommended Schedule:

  • Grant: 00:00 UTC - Start of day ensures credits available early
  • Expire: 01:00 UTC - After grants, prevents race conditions

Why separate times?

  • Prevents conflicting database updates
  • Grants process first, then expiration cleanup
  • Easier to debug if issues occur

2. Monitoring

Set up alerts for:

  • ✅ Job execution failures
  • ✅ Response time > 5 seconds
  • ✅ Error rate > 1%
  • ✅ No successful execution for 24 hours

3. Backup Strategy

Configure redundant cron jobs on different platforms:

  • Primary: Vercel Cron Jobs (if on Vercel)
  • Backup: Upstash QStash or cron-job.org

If primary fails, backup ensures continuity.

4. Database Connection

Ensure your database can handle concurrent connections:

// In production, use connection pooling
const pool = new Pool({
  max: 20, // Maximum connections
  idleTimeoutMillis: 30000,
  connectionTimeoutMillis: 2000,
});

🔐 Security Best Practices

  1. Rotate CRON_SECRET regularly (every 90 days)
  2. Use different secrets for production and staging
  3. Monitor for unauthorized requests:
    if (authHeader !== `Bearer ${process.env.CRON_SECRET}`) {
      console.error(`[Security] Unauthorized cron attempt from ${request.ip}`);
      // Consider implementing rate limiting
    }
  4. Implement IP whitelist (if platform supports):
    const allowedIPs = ['1.2.3.4', '5.6.7.8'];
    if (!allowedIPs.includes(request.ip)) {
      return NextResponse.json({ error: 'Forbidden' }, { status: 403 });
    }