We often get asked by our users if we can provide notifications for the customers that are approaching a limit in their products. Visibility into that type of information is incredibly useful, particularly for startups trying to identify their most active users or those ready for an upsell conversation.
However, it’s often challenging data both to get and to visualize in a user-friendly way that folks can act on. That’s because this type of data is often locked in application databases that need to be queried carefully & manually. Fortunately, Schematic supports metered features, and tracks data about limits and utilization on a per feature and per customer basis, which can be retrieved via our API.
In this tutorial, we'll build a small app that monitors feature usage across a customer base and triggers webhook notifications when customers are reaching usage limits. We'll use Next.js, Schematic's API to retrieve allocation and usage data for individual features, and webhook notifications for alerting.
In the future, Schematic will natively support triggers for limits, but we can easily implement something similar with data from Schematic today and customize to your specific use case.
You can find all the code associated with this tutorial here.
We'll create a dashboard that:
Displays feature usage across all companies using your product
Automatically polls for updates every 5 minutes
Triggers webhook notifications at 80%, 90%, and 100% usage thresholds
Provides a log of all threshold notifications
Before starting, you'll need:
Node.js 18 or higher installed
A Schematic account that’s instrumented with metered features
A webhook endpoint for receiving notifications (optional)
Create a new Next.js project:
1npx create-next-app@latest usage-monitor --typescript --tailwind --eslint
2cd /usage-monitor
Install dependencies:
1npx shadcn@latest init
2npx shadcn@latest add card alert input button tabs
3npm install @schematichq/schematic-typescript-node
Create an API route to handle feature usage data from Schematic:
1// app/api/feature-usage/route.ts
2import { NextRequest, NextResponse } from "next/server";
3import { SchematicClient } from "@schematichq/schematic-typescript-node";
4
5const DEFAULT_PAGE_SIZE: number = 100;
6
7export async function GET(request: NextRequest) {
8 const apiKey = process.env.SCHEMATIC_SECRET_KEY;
9 if (!apiKey) {
10 return NextResponse.json({ message: "No Schematic key" }, { status: 400 });
11 }
12
13 const searchParams = request.nextUrl.searchParams;
14 const featureId = searchParams.get('featureId');
15 const offset = parseInt(searchParams.get('offset') || '0');
16 const limit = parseInt(searchParams.get('limit') || String(DEFAULT_PAGE_SIZE));
17
18 try {
19 const schematicClient = new SchematicClient({ apiKey });
20 const response = await schematicClient.entitlements.listFeatureCompanies({
21 featureId,
22 limit,
23 offset,
24 });
25
26 return NextResponse.json({
27 data: response.data,
28 pagination: {
29 offset,
30 limit,
31 total: response.data.length,
32 hasMore: response.data.length === limit
33 }
34 });
35 } catch (error) {
36 return NextResponse.json(
37 { message: "Failed to fetch feature usage data" },
38 { status: 500 }
39 );
40 }
41}
Create a notification endpoint that triggers webhooks at 80%, 90%, and 100% of a given limit for a feature:
1// app/api/usage-notifications/route.ts
2import { NextRequest, NextResponse } from "next/server";
3import { SchematicClient } from "@schematichq/schematic-typescript-node";
4
5const THRESHOLDS = [80, 90, 100];
6
7export async function POST(request: NextRequest) {
8 const apiKey = process.env.SCHEMATIC_SECRET_KEY;
9 if (!apiKey) {
10 return NextResponse.json({ message: "No Schematic key" }, { status: 400 });
11 }
12
13 const { featureId } = await request.json();
14 const schematicClient = new SchematicClient({ apiKey });
15
16 try {
17 const response = await schematicClient.entitlements.listFeatureCompanies({
18 featureId,
19 });
20
21 const notifications = [];
22
23 for (const company of response.data) {
24 const usagePercentage = (company.usage / (company.allocation || 1)) * 100;
25
26 for (const threshold of THRESHOLDS) {
27 if (usagePercentage >= threshold) {
28 notifications.push({
29 companyId: company.company.id,
30 companyName: company.company.name,
31 threshold,
32 usage: company.usage,
33 allocation: company.allocation,
34 timestamp: new Date().toISOString()
35 });
36
37 if (process.env.WEBHOOK_URL) {
38 await fetch(process.env.WEBHOOK_URL, {
39 method: 'POST',
40 headers: { 'Content-Type': 'application/json' },
41 body: JSON.stringify({
42 companyId: company.company.id,
43 threshold,
44 usage: company.usage,
45 allocation: company.allocation
46 })
47 });
48 }
49 }
50 }
51 }
52
53 return NextResponse.json({ notifications });
54 } catch (error) {
55 return NextResponse.json(
56 { message: "Failed to process notifications" },
57 { status: 500 }
58 );
59 }
60}
Create types for the Schematic API response:
1export interface Company {
2 company: {
3 id: string;
4 name: string;
5 logo_url: string;
6 plan?: {
7 name: string;
8 };
9 };
10 period: string;
11 usage: number;
12 allocation: number | null;
13}
14
15export interface PaginationState {
16 offset: number;
17 limit: number;
18 total?: number;
19 hasMore: boolean;
20}
21
22export interface ApiResponse {
23 data: Company[];
24 pagination: PaginationState;
25}
This dashboard component fetches and displays usage data, sorted by utilization percentage:
1export interface CompanyUsageListProps {
2 data: ApiResponse | null;
3 addWebhookLog: (log: WebhookLog) => void;
4}
5
6export const CompanyUsageList = ({
7 data,
8 addWebhookLog
9}: CompanyUsageListProps) => {
10 const companies = data?.data || [];
11
12 // Sort companies by utilization percentage
13 const sortedCompanies = [...companies].sort((a, b) => {
14 const percentageA = calculateUsagePercentage(a.usage, a.allocation);
15 const percentageB = calculateUsagePercentage(b.usage, b.allocation);
16 return percentageB - percentageA;
17 });
18
19 return (
20 <div className="space-y-4">
21 {sortedCompanies.map((company) => {
22 const usagePercentage = calculateUsagePercentage(company.usage, company.allocation);
23
24 return (
25 // ... company usage list layout ...
26 );
27};
Set up threshold checking and webhook notifications:
1useEffect(() => {
2 const checkUsageAndNotifications = async () => {
3 await fetchData(featureId);
4 // Call our notifications endpoint
5 try {
6 const response = await fetch('/api/usage-notifications', {
7 method: 'POST',
8 headers: {
9 'Content-Type': 'application/json',
10 },
11 body: JSON.stringify({ featureId })
12 });
13
14 return () => clearInterval(interval);
15 }, [featureId]);
Implement automatic refresh of data from Schematic:
1const POLLING_INTERVAL: number = 5 * 60 * 1000; // 5 minutes
2
3export const CompanyUsageApp = () => {
4 // ... state setup ...
5
6 const setupPolling = useCallback(() => {
7 if (pollInterval.current) {
8 clearInterval(pollInterval.current);
9 }
10
11 pollInterval.current = setInterval(() => {
12 fetchData(featureId);
13 }, POLLING_INTERVAL);
14
15 return () => {
16 if (pollInterval.current) {
17 clearInterval(pollInterval.current);
18 }
19 };
20 }, [featureId]);
21
22 // ... rest of component
23};
Pull together the full dashboard component. It will include:
An input field for the feature ID
List of companies with utilization and allocation
Pagination support
The code for that is here.
There are several next steps you could take to improve upon this, including:
Adding email notifications that go directly to account owners
Implementing custom threshold settings
Support for monitoring every metered feature at once, rather than one at a time
Adding historical usage tracking for trend analysis
Adding database persistence for notification history