API Routes Reference
Overview
Novaqy Care Connect provides RESTful API routes for payments, leads, and OAuth integrations.
Authentication
API Key Authentication
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:
Response:
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:
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: Success201: Created400: Bad Request401: Unauthorized403: Forbidden404: Not Found429: Rate Limit Exceeded500: 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 }
);
}