Protecting an API with payments
Gate your API endpoints behind on-chain payments. Users pay once, then access is automatically unlocked based on payment verification.
This guide shows you how to protect API endpoints by requiring a valid payment before granting access. Perfect for premium features, API access, or digital product delivery.
1
Create a payment endpoint
Create an API endpoint that generates a payment request. This endpoint should create a payment with metadata that identifies the user and the resource they're paying for.
typescript
// app/api/payments/create/route.ts
import { ZynPayClient, Environment } from '@zyntrialabs/zynpay-sdk'
import { NextRequest, NextResponse } from 'next/server'
export async function POST(request: NextRequest) {
const { userId, resourceId, amount } = await request.json()
const client = new ZynPayClient({
merchantWallet: process.env.MERCHANT_WALLET_ADDRESS!,
environment: Environment.TESTNET,
defaultChain: 'base',
})
// Create payment with metadata
const payment = client.createPayment(amount, 'base', {
userId,
resourceId,
description: `Access to resource ${resourceId}`,
metadata: {
userId,
resourceId,
type: 'api_access',
},
})
// Store payment in your database
await db.payments.create({
paymentId: payment.id,
userId,
resourceId,
amount,
status: 'pending',
})
return NextResponse.json({
paymentId: payment.id,
amount: payment.amount,
merchantAddress: payment.merchantAddress,
})
}2
Create a payment verification middleware
Create middleware that checks if a user has a valid payment for the requested resource before allowing API access.
typescript
// middleware/payment-verification.ts
import { ZynPayClient, Environment } from '@zyntrialabs/zynpay-sdk'
import { JsonRpcProvider } from 'ethers'
export async function verifyPaymentAccess(
userId: string,
resourceId: string,
txHash?: string
): Promise<boolean> {
// Check database for completed payment
const payment = await db.payments.findFirst({
where: {
userId,
resourceId,
status: 'completed',
},
})
if (!payment) {
return false
}
// Optionally verify on-chain if txHash provided
if (txHash) {
const provider = new JsonRpcProvider('https://sepolia.base.org')
const receipt = await provider.getTransactionReceipt(txHash)
if (!receipt || receipt.status !== 1) {
return false
}
}
return true
}
// Usage in API route
export async function GET(request: NextRequest) {
const userId = request.headers.get('x-user-id')
const resourceId = request.nextUrl.searchParams.get('resourceId')
if (!userId || !resourceId) {
return NextResponse.json({ error: 'Missing parameters' }, { status: 400 })
}
// Verify payment
const hasAccess = await verifyPaymentAccess(userId, resourceId)
if (!hasAccess) {
return NextResponse.json(
{ error: 'Payment required', paymentUrl: '/pay?resourceId=' + resourceId },
{ status: 402 }
)
}
// User has paid, grant access
return NextResponse.json({ data: 'Your protected resource data' })
}3
Handle payment webhooks
Set up a webhook endpoint that automatically updates payment status when transactions are confirmed on-chain.
typescript
// app/api/webhooks/payment/route.ts
import { NextRequest, NextResponse } from 'next/server'
import { ZynPayClient, Environment } from '@zyntrialabs/zynpay-sdk'
export async function POST(request: NextRequest) {
const { paymentId, txHash, status } = await request.json()
// Verify webhook signature (implement your own verification)
// const isValid = verifyWebhookSignature(request)
// if (!isValid) return NextResponse.json({ error: 'Invalid signature' }, { status: 401 })
// Update payment status in database
await db.payments.update({
where: { paymentId },
data: {
status: status === 'completed' ? 'completed' : 'failed',
txHash,
completedAt: status === 'completed' ? new Date() : null,
},
})
// If payment completed, unlock access
if (status === 'completed') {
const payment = await db.payments.findUnique({ where: { paymentId } })
// Unlock user access
await unlockUserAccess(payment.userId, payment.resourceId)
// Send notification
await sendNotification(payment.userId, 'Payment confirmed! Access unlocked.')
}
return NextResponse.json({ success: true })
}
async function unlockUserAccess(userId: string, resourceId: string) {
// Your business logic: update user permissions, grant API access, etc.
await db.userAccess.create({
userId,
resourceId,
grantedAt: new Date(),
})
}Example: Protecting a premium API endpoint
typescript
// app/api/premium/data/route.ts
import { NextRequest, NextResponse } from 'next/server'
import { verifyPaymentAccess } from '@/middleware/payment-verification'
export async function GET(request: NextRequest) {
const userId = request.headers.get('x-user-id')
// Check if user has paid for premium access
const hasAccess = await verifyPaymentAccess(userId, 'premium_api')
if (!hasAccess) {
return NextResponse.json(
{
error: 'Premium access required',
message: 'Please complete payment to access this endpoint',
paymentUrl: '/api/payments/create?resourceId=premium_api',
},
{ status: 402 }
)
}
// User has paid, return premium data
return NextResponse.json({
data: {
// Your premium API response
premiumFeature: true,
exclusiveData: '...',
},
})
}