Skip to content

API Routes Reference

Overview

Novaqy Care Connect provides RESTful API routes for payments, leads, and OAuth integrations.

Authentication

API Key Authentication

headers: {
  'Authorization': `Bearer ${process.env.SUPABASE_SERVICE_ROLE_KEY}`
}

Session Authentication

Uses Next.js middleware to verify Supabase auth session.

Payment Routes

POST /api/payments/create-order

Create a Razorpay order for checkout.

Request Body:

{
  "planId": "uuid",
  "userId": "uuid",
  "currency": "USD"
}

Response:

{
  "orderId": "order_abc123",
  "amount": 9900,
  "currency": "USD",
  "keyId": "rzp_live_xxx"
}

Example:

const response = await fetch('/api/payments/create-order', {
  method: 'POST',
  headers: { 'Content-Type': 'application/json' },
  body: JSON.stringify({
    planId: plan.id,
    userId: user.id,
    currency: 'USD'
  })
});
const data = await response.json();

POST /api/payments/webhook

Razorpay webhook endpoint for payment confirmations.

Headers Required: - x-razorpay-signature: Webhook signature for verification

Event Types: - payment.captured: Payment successful - payment.failed: Payment failed - subscription.activated: Subscription started - subscription.cancelled: Subscription ended

Webhook Verification:

import crypto from 'crypto';

const secret = process.env.RAZORPAY_KEY_SECRET;
const signature = req.headers['x-razorpay-signature'];
const body = JSON.stringify(req.body);

const expectedSignature = crypto
  .createHmac('sha256', secret)
  .update(body)
  .digest('hex');

if (signature === expectedSignature) {
  // Valid webhook
}

Lead Routes

POST /api/leads/callback

Capture lead from callback form submission.

Request Body:

{
  "name": "John Doe",
  "phone": "+1234567890",
  "email": "john@example.com",
  "source": "pricing_page",
  "utm_campaign": "summer_sale",
  "utm_medium": "cpc",
  "utm_source": "google"
}

Response:

{
  "success": true,
  "leadId": "uuid",
  "message": "Lead captured successfully"
}

Example:

const response = await fetch('/api/leads/callback', {
  method: 'POST',
  headers: { 'Content-Type': 'application/json' },
  body: JSON.stringify({
    name: formData.name,
    phone: formData.phone,
    email: formData.email,
    source: 'contact_form'
  })
});

OAuth Routes

GET /api/oauth/callback

OAuth callback for Zoho CRM integration.

Query Parameters: - code: Authorization code from Zoho - state: CSRF token for validation

Response: - Redirects to dashboard with auth token

Example Flow:

// 1. Redirect user to Zoho
const authUrl = `https://accounts.zoho.com/oauth/v2/auth?` +
  `client_id=${ZOHO_CLIENT_ID}&` +
  `redirect_uri=${ZOHO_REDIRECT_URI}&` +
  `response_type=code&` +
  `scope=ZohoCRM.modules.ALL`;

// 2. User authorizes, Zoho redirects to callback
// 3. Exchange code for token
const tokenResponse = await fetch('https://accounts.zoho.com/oauth/v2/token', {
  method: 'POST',
  body: new URLSearchParams({
    code: req.query.code,
    client_id: ZOHO_CLIENT_ID,
    client_secret: ZOHO_CLIENT_SECRET,
    redirect_uri: ZOHO_REDIRECT_URI,
    grant_type: 'authorization_code'
  })
});

Supabase REST API

Direct Database Queries

Get Plans:

const { data, error } = await supabase
  .from('plans')
  .select('*')
  .eq('is_active', true)
  .order('price_usd', { ascending: true });

Get User Subscriptions:

const { data, error } = await supabase
  .from('subscriptions')
  .select(`
    *,
    plan:plans(name, price_usd),
    user:users(email, full_name)
  `)
  .eq('user_id', userId)
  .eq('status', 'active')
  .single();

Insert Lead:

const { data, error } = await supabase
  .from('leads')
  .insert({
    email: 'john@example.com',
    full_name: 'John Doe',
    phone: '+1234567890',
    source: 'landing_page',
    utm_campaign: 'summer_sale'
  })
  .select()
  .single();

Rate Limiting

Current Limits

  • Unauthenticated: 10 requests/minute
  • Authenticated: 100 requests/minute
  • Service Role: Unlimited

Implementation

import { Ratelimit } from '@upstash/ratelimit';
import { Redis } from '@upstash/redis';

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

export async function POST(req: Request) {
  const ip = req.headers.get('x-forwarded-for') ?? 'unknown';
  const { success } = await ratelimit.limit(ip);

  if (!success) {
    return Response.json(
      { error: 'Rate limit exceeded' },
      { status: 429 }
    );
  }

  // Process request
}

Error Handling

Standard Error Response

{
  "error": {
    "code": "PAYMENT_FAILED",
    "message": "Payment processing failed",
    "details": "Insufficient funds"
  }
}

HTTP Status Codes

  • 200: Success
  • 201: Created
  • 400: Bad Request
  • 401: Unauthorized
  • 403: Forbidden
  • 404: Not Found
  • 429: Rate Limit Exceeded
  • 500: Internal Server Error

Webhooks

n8n Integration

Payment Webhook:

await fetch(process.env.N8N_PAYMENT_WEBHOOK_URL, {
  method: 'POST',
  headers: { 'Content-Type': 'application/json' },
  body: JSON.stringify({
    event: 'payment.success',
    payment: {
      id: payment.id,
      amount: payment.amount,
      user_id: payment.user_id
    }
  })
});

Lead Webhook:

await fetch(process.env.N8N_LEAD_WEBHOOK_URL, {
  method: 'POST',
  headers: { 'Content-Type': 'application/json' },
  body: JSON.stringify({
    event: 'lead.created',
    lead: {
      email: lead.email,
      source: lead.source
    }
  })
});

Testing

Local Testing

# Start dev server
npm run dev

# Test payment endpoint
curl -X POST http://localhost:3000/api/payments/create-order \
  -H "Content-Type: application/json" \
  -d '{"planId":"uuid","userId":"uuid","currency":"USD"}'

Webhook Testing

Use Razorpay webhook testing tool:

curl -X POST http://localhost:3000/api/payments/webhook \
  -H "x-razorpay-signature: test_signature" \
  -H "Content-Type: application/json" \
  -d @webhook-payload.json

Security

CSRF Protection

import { verifyCsrfToken } from '@/lib/csrf';

export async function POST(req: Request) {
  const token = req.headers.get('x-csrf-token');
  if (!verifyCsrfToken(token)) {
    return Response.json({ error: 'Invalid CSRF token' }, { status: 403 });
  }
  // Process request
}

Input Validation

import { z } from 'zod';

const createOrderSchema = z.object({
  planId: z.string().uuid(),
  userId: z.string().uuid(),
  currency: z.enum(['USD', 'CAD', 'INR'])
});

const result = createOrderSchema.safeParse(req.body);
if (!result.success) {
  return Response.json(
    { error: result.error.issues },
    { status: 400 }
  );
}

Next Steps