> ## Documentation Index
> Fetch the complete documentation index at: https://docs.schedo.dev/llms.txt
> Use this file to discover all available pages before exploring further.

# Next.js Integration

> Run scheduled tasks on Next.js with webhook-based cron jobs

# Next.js Integration

Run reliable scheduled tasks on Next.js with webhook-based cron jobs. Perfect for serverless applications with built-in signature verification and monitoring.

## Overview

Schedo integrates seamlessly with Next.js through webhook-based scheduling. Instead of traditional cron jobs that require persistent processes, Schedo sends HTTP requests to your Next.js API routes at scheduled times.

### Key Benefits

* **Serverless-first**: Works perfectly with Next.js serverless functions
* **No infrastructure**: No need for external cron services or GitHub Actions
* **Secure**: Built-in cryptographic signature verification
* **Monitored**: Comprehensive webhook delivery tracking with Schedo.dev dashboard
* **Simple**: Just one API endpoint to get started
* **Full visibility**: Track execution times, success rates, and error details

## Quick Start

### 1. Install the SDK

<CodeGroup>
  ```bash npm theme={null}
  npm install @useschedo/node-sdk
  ```

  ```bash yarn theme={null}
  yarn add @useschedo/node-sdk
  ```

  ```bash pnpm theme={null}
  pnpm add @useschedo/node-sdk
  ```
</CodeGroup>

### 2. Create an API Route

Create a webhook endpoint in your Next.js application:

<CodeGroup>
  ```typescript App Router (app/api/cron/route.ts) theme={null}
  import { NextResponse } from "next/server";
  import type { NextRequest } from "next/server";
  import { SignatureVerifier } from "@useschedo/node-sdk";

  const verifier = new SignatureVerifier(
    process.env.SCHEDO_SIGNATURE_KEY!
  );

  export async function POST(req: NextRequest) {
    try {
      const body = await req.json();

      // Verify webhook signature
      const signature = req.headers.get("x-schedo-signature");
      if (!signature) {
        return NextResponse.json(
          { error: "Missing signature" },
          { status: 401 }
        );
      }

      const isValid = verifier.verifyBytesSignature(
        Buffer.from(JSON.stringify(body)),
        signature
      );

      if (!isValid) {
        return NextResponse.json(
          { error: "Invalid signature" },
          { status: 401 }
        );
      }

      // Your scheduled task logic here
      await processScheduledTask(body);

      return NextResponse.json({
        success: true,
        timestamp: new Date().toISOString()
      });
    } catch (error) {
      console.error("Cron job error:", error);
      return NextResponse.json(
        { error: "Internal server error" },
        { status: 500 }
      );
    }
  }

  async function processScheduledTask(payload: any) {
    // Example: Send daily reports
    console.log("Processing scheduled task:", payload);

    // Your business logic here
    // - Send emails
    // - Process data
    // - Update database
    // - Call external APIs
  }
  ```

  ```typescript Pages Router (pages/api/cron.ts) theme={null}
  import type { NextApiRequest, NextApiResponse } from 'next';
  import { SignatureVerifier } from "@useschedo/node-sdk";

  const verifier = new SignatureVerifier(
    process.env.SCHEDO_SIGNATURE_KEY!
  );

  export default async function handler(
    req: NextApiRequest,
    res: NextApiResponse
  ) {
    if (req.method !== 'POST') {
      return res.status(405).json({ error: 'Method not allowed' });
    }

    try {
      // Verify webhook signature
      const signature = req.headers['x-schedo-signature'] as string;
      if (!signature) {
        return res.status(401).json({ error: 'Missing signature' });
      }

      const isValid = verifier.verifyBytesSignature(
        Buffer.from(JSON.stringify(req.body)),
        signature
      );

      if (!isValid) {
        return res.status(401).json({ error: 'Invalid signature' });
      }

      // Your scheduled task logic here
      await processScheduledTask(req.body);

      res.status(200).json({
        success: true,
        timestamp: new Date().toISOString()
      });
    } catch (error) {
      console.error('Cron job error:', error);
      res.status(500).json({ error: 'Internal server error' });
    }
  }

  async function processScheduledTask(payload: any) {
    // Your business logic here
    console.log("Processing scheduled task:", payload);
  }
  ```
</CodeGroup>

### 3. Set Environment Variables

Add your Schedo signature key to your environment variables:

```bash .env.local theme={null}
SCHEDO_SIGNATURE_KEY=your_signature_key_here
```

<Note>
  Get your signature key from the [Schedo Dashboard](https://app.schedo.dev) under Settings > API Keys.
</Note>

### 4. Deploy Your Application

Deploy your Next.js application to your preferred hosting platform:

<CodeGroup>
  ```bash Platform Deployment theme={null}
  npm run build
  npm run start
  ```

  ```bash Docker Build theme={null}
  docker build -t my-nextjs-app .
  docker run -p 3000:3000 my-nextjs-app
  ```

  ```bash Cloud Deployment theme={null}
  # Deploy to your preferred cloud platform
  # Examples: Vercel, Netlify, Railway, AWS, etc.
  ```
</CodeGroup>

### 5. Create a Webhook Job

In the Schedo dashboard:

1. Click "Create Webhook Job"
2. Enter your endpoint URL: `https://your-app.com/api/cron`
3. Set your schedule (e.g., `0 9 * * *` for daily at 9 AM)
4. Click "Create"

## Advanced Configuration

### Custom Headers

Add custom headers to your webhook requests:

```javascript theme={null}
// In Schedo dashboard, add custom headers
{
  "Authorization": "Bearer your-token",
  "Content-Type": "application/json"
}
```

### Request Payload

Customize the payload sent to your endpoint:

```json theme={null}
{
  "jobId": "job_123",
  "scheduledAt": "2024-01-15T09:00:00Z",
  "customData": {
    "reportType": "daily",
    "recipients": ["admin@company.com"]
  }
}
```

### Error Handling

Implement robust error handling in your API route:

```typescript theme={null}
export async function POST(req: NextRequest) {
  try {
    // ... verification code ...

    const result = await processScheduledTask(body);

    return NextResponse.json({
      success: true,
      result: result,
      processedAt: new Date().toISOString()
    });
  } catch (error) {
    // Log detailed error information
    console.error("Scheduled task failed:", {
      error: error.message,
      stack: error.stack,
      payload: body,
      timestamp: new Date().toISOString()
    });

    // Return error response (Schedo will retry based on your settings)
    return NextResponse.json(
      {
        error: "Task execution failed",
        details: error.message
      },
      { status: 500 }
    );
  }
}
```

## Use Cases

### Daily Reports

```typescript theme={null}
async function sendDailyReport(payload: any) {
  const yesterday = new Date();
  yesterday.setDate(yesterday.getDate() - 1);

  // Fetch data
  const analytics = await getAnalytics(yesterday);

  // Generate report
  const report = await generateReport(analytics);

  // Send via email
  await sendEmail({
    to: payload.customData.recipients,
    subject: `Daily Report - ${yesterday.toDateString()}`,
    html: report
  });
}
```

### Database Cleanup

```typescript theme={null}
async function cleanupOldData(payload: any) {
  const cutoffDate = new Date();
  cutoffDate.setDays(cutoffDate.getDate() - 30);

  // Clean up old logs
  await db.logs.deleteMany({
    createdAt: { lt: cutoffDate }
  });

  // Clean up temporary files
  await cleanupTempFiles(cutoffDate);

  console.log(`Cleanup completed: removed data older than ${cutoffDate}`);
}
```

### API Synchronization

```typescript theme={null}
async function syncExternalData(payload: any) {
  try {
    // Fetch from external API
    const externalData = await fetch('https://api.external.com/data', {
      headers: { 'Authorization': `Bearer ${process.env.EXTERNAL_API_KEY}` }
    }).then(res => res.json());

    // Update local database
    await db.externalData.upsert({
      where: { id: externalData.id },
      update: externalData,
      create: externalData
    });

    console.log(`Synced ${externalData.length} records`);
  } catch (error) {
    console.error('Sync failed:', error);
    throw error; // Will trigger retry in Schedo
  }
}
```

## Deployment Platforms

Schedo webhooks work with any hosting platform that supports Next.js API routes. **For optimal performance and reliability, we recommend [Sherpa.sh](https://sherpa.sh)** - our preferred Next.js hosting partner with container-first architecture and no function timeouts.

Here are configuration examples for popular platforms:

### Serverless Platforms

For serverless deployments, configure function timeouts appropriately:

```json Platform Configuration theme={null}
{
  "functions": {
    "pages/api/cron.js": {
      "maxDuration": 30
    }
  }
}
```

### Container Platforms

For containerized deployments:

```dockerfile Dockerfile theme={null}
FROM node:18-alpine
WORKDIR /app
COPY package*.json ./
RUN npm install
COPY . .
RUN npm run build
EXPOSE 3000
CMD ["npm", "start"]
```

### Platform-Specific Notes

* **Serverless Functions**: Most platforms limit execution time (typically 10-60 seconds)
* **Container Deployments**: No execution time limits, better for long-running tasks
* **Static Hosting**: Ensure API routes are properly configured and deployed

<Note>
  **Recommended**: [Sherpa.sh](https://sherpa.sh) offers the best experience for Schedo.dev integration with container-based deployments, no timeout limits, and enhanced monitoring. Schedo.dev actively develops new features with Sherpa.sh compatibility first. [Learn more about Sherpa.sh integration](/integrations/sherpa).
</Note>

## Security Best Practices

### Environment Variables

Always store sensitive data in environment variables:

```bash theme={null}
# Production environment
SCHEDO_SIGNATURE_KEY=your_production_key
DATABASE_URL=your_database_url
EMAIL_API_KEY=your_email_key
```

### Signature Verification

Always verify webhook signatures:

```typescript theme={null}
// ❌ Don't skip signature verification
export async function POST(req: NextRequest) {
  // Process without verification - DANGEROUS!
  await processScheduledTask(await req.json());
}

// ✅ Always verify signatures
export async function POST(req: NextRequest) {
  const isValid = verifier.verifyBytesSignature(/* ... */);
  if (!isValid) {
    return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
  }
  // Safe to process
}
```

### Rate Limiting

Implement rate limiting for additional security:

```typescript theme={null}
import { Ratelimit } from "@upstash/ratelimit";
import { Redis } from "@upstash/redis";

const redis = new Redis({
  url: process.env.REDIS_URL,
  token: process.env.REDIS_TOKEN,
});

const ratelimit = new Ratelimit({
  redis: redis,
  limiter: Ratelimit.slidingWindow(10, "1 m"),
});

export async function POST(req: NextRequest) {
  const ip = req.ip ?? "127.0.0.1";
  const { success } = await ratelimit.limit(ip);

  if (!success) {
    return NextResponse.json({ error: "Rate limited" }, { status: 429 });
  }

  // Continue processing...
}
```

### IP Allowlisting

For enhanced security, you can restrict your cron endpoints to only accept requests from Schedo.dev servers. This prevents unauthorized access to your webhook endpoints.

<Note>
  IP allowlisting is available as an enterprise feature. Contact our support team at [support@schedo.dev](mailto:support@schedo.dev) to enable IP allowlisting for your account and receive the current list of Schedo.dev IP addresses.
</Note>

Once enabled, you can implement IP filtering in your Next.js application:

```typescript theme={null}
const ALLOWED_IPS = [
  // IPs will be provided by Schedo.dev support team
  '10.0.0.1',
  '10.0.0.2',
  // Add more IPs as provided
];

export async function POST(req: NextRequest) {
  const clientIP = req.ip ?? req.headers.get('x-forwarded-for') ?? '127.0.0.1';

  if (!ALLOWED_IPS.includes(clientIP)) {
    console.warn(`Blocked request from unauthorized IP: ${clientIP}`);
    return NextResponse.json({ error: "Unauthorized" }, { status: 403 });
  }

  // Continue with signature verification and processing...
}
```

Benefits of IP allowlisting:

* **Additional Security Layer**: Prevents direct access to your webhook endpoints
* **Compliance**: Meets security requirements for sensitive applications
* **Audit Trail**: Clear logging of blocked unauthorized requests
* **Peace of Mind**: Ensures only Schedo.dev can trigger your scheduled tasks

## Monitoring and Debugging

### Schedo.dev Dashboard Monitoring

Schedo.dev provides comprehensive monitoring and visibility for your Next.js cron jobs directly in the dashboard:

* **Execution History**: View all webhook calls with timestamps and response codes
* **Success/Failure Rates**: Track job reliability over time
* **Response Times**: Monitor your endpoint performance
* **Error Details**: See detailed error messages and stack traces
* **Retry Attempts**: Track failed requests and retry patterns
* **Payload Inspection**: View request and response data for debugging

The dashboard automatically captures and displays:

* HTTP status codes from your Next.js endpoint
* Response times and payload sizes
* Error messages and failure reasons
* Execution trends and patterns

### Application-Level Logging

Implement comprehensive logging in your Next.js application:

```typescript theme={null}
export async function POST(req: NextRequest) {
  const startTime = Date.now();
  const requestId = crypto.randomUUID();

  console.log(`[${requestId}] Webhook received`, {
    headers: Object.fromEntries(req.headers.entries()),
    timestamp: new Date().toISOString()
  });

  try {
    const result = await processScheduledTask(body);

    console.log(`[${requestId}] Task completed successfully`, {
      duration: Date.now() - startTime,
      result: result
    });

    return NextResponse.json({ success: true, requestId });
  } catch (error) {
    console.error(`[${requestId}] Task failed`, {
      error: error.message,
      duration: Date.now() - startTime
    });

    throw error;
  }
}
```

### Schedo.dev Monitoring & Visibility

Schedo.dev provides comprehensive monitoring and visibility for your Next.js cron jobs directly in the dashboard. This gives you complete insight into your webhook executions without needing to implement custom monitoring solutions.

#### Dashboard Features

**Real-time Monitoring**

* Live webhook execution status
* Success/failure rates and trends
* Average response times and performance metrics
* Active job status and next scheduled execution times

**Detailed Execution History**

* Complete log of all webhook calls with timestamps
* HTTP status codes and response details
* Request and response payload inspection
* Error messages and stack traces for failed executions

**Performance Analytics**

* Response time trends over different time periods
* Success rate percentages and reliability metrics
* Retry attempt tracking and patterns
* Webhook delivery success rates

**Error Tracking & Debugging**

* Detailed error messages from your Next.js endpoints
* Failed request retry attempts and outcomes
* Webhook timeout and connection error tracking

## Troubleshooting

### Common Issues

#### Signature Verification Fails

```typescript theme={null}
// Make sure to stringify the body exactly as received
const bodyString = JSON.stringify(body);
const isValid = verifier.verifyBytesSignature(
  Buffer.from(bodyString),
  signature
);
```

#### Function Timeout

```typescript theme={null}
// For long-running tasks, break them into smaller chunks
async function processLargeDataset(data: any[]) {
  const batchSize = 100;

  for (let i = 0; i < data.length; i += batchSize) {
    const batch = data.slice(i, i + batchSize);
    await processBatch(batch);

    // Prevent timeout
    if (i % 500 === 0) {
      await new Promise(resolve => setTimeout(resolve, 100));
    }
  }
}
```

#### Memory Limits

```typescript theme={null}
// Stream large data instead of loading everything into memory
import { Readable } from 'stream';

async function processLargeFile() {
  const stream = createReadStream('large-file.json');

  for await (const chunk of stream) {
    await processChunk(chunk);
  }
}
```

### Debug Mode

Enable debug logging in development:

```typescript theme={null}
const DEBUG = process.env.NODE_ENV === 'development';

if (DEBUG) {
  console.log('Debug: Request headers', req.headers);
  console.log('Debug: Request body', body);
}
```

## Migration Guide

### From Platform-Specific Cron

If you're migrating from platform-specific cron solutions:

```typescript theme={null}
// Before (Platform-specific cron)
export async function GET() {
  await runScheduledTask();
  return Response.json({ success: true });
}

// After (Schedo webhook)
export async function POST(req: NextRequest) {
  // Add signature verification
  const isValid = verifier.verifyBytesSignature(/* ... */);
  if (!isValid) {
    return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
  }

  await runScheduledTask();
  return NextResponse.json({ success: true });
}
```

### From GitHub Actions

Replace GitHub Actions cron with Schedo webhooks:

```yaml theme={null}
# Remove this from .github/workflows/cron.yml
name: Scheduled Task
on:
  schedule:
    - cron: '0 9 * * *'
jobs:
  run:
    runs-on: ubuntu-latest
    steps:
      - name: Trigger endpoint
        run: curl -X POST ${{ secrets.WEBHOOK_URL }}
```

```typescript theme={null}
// Add this to your Next.js app instead
export async function POST(req: NextRequest) {
  // Your scheduled task logic here
  await runScheduledTask();
  return NextResponse.json({ success: true });
}
```

## FAQ

<AccordionGroup>
  <Accordion title="Do I need to keep a server running for cron jobs?">
    No! That's the beauty of webhook-based scheduling. Your Next.js functions only run when Schedo triggers them, making it perfect for serverless architectures.
  </Accordion>

  <Accordion title="How secure are the webhook requests?">
    Very secure. Every request includes a cryptographic signature that you must verify. This ensures only legitimate Schedo requests can trigger your endpoints.
  </Accordion>

  <Accordion title="Can I use this with Next.js Edge Functions?">
    Yes! The webhook approach works with both Node.js runtime and Edge Runtime functions in Next.js.
  </Accordion>

  <Accordion title="What about Next.js function execution limits?">
    Most serverless platforms have execution time limits (typically 10-60 seconds). For longer tasks, consider breaking them into smaller chunks or using a queue system.
  </Accordion>

  <Accordion title="How do I handle failures and retries?">
    Return appropriate HTTP status codes from your endpoint. Schedo will automatically retry failed requests based on your retry configuration.
  </Accordion>

  <Accordion title="Can I schedule multiple different tasks?">
    Yes! You can create multiple webhook jobs pointing to different endpoints, or use a single endpoint with custom payload data to determine which task to run.
  </Accordion>
</AccordionGroup>

## Next Steps

* [SDK Reference](/sdks/node)
