Skip to main content
Send notifications to users who have installed your app in the Base App using the Base.dev REST API. No webhooks, no database, no token management.
Notifications are scoped to your app. Your API key only returns users who opted into your app and can only send to those users.

Quickstart

1

Generate your API key

Go to your project on Base.dev, open Settings > API Key, and generate a new key. Add it to your environment:
.env
BASE_DEV_API_KEY=bdev_your_api_key_here
Never commit API keys to version control.
2

Query your opted-in users

curl "https://www.base.dev/v1/notifications/app/users?app_url=https://your-app.com&notification_enabled=true" \
  -H "x-api-key: $BASE_DEV_API_KEY"
{
  "success": true,
  "users": [
    { "address": "0xc0c3132DFc4929bb6F68FA76B8fB379fF8c5bE74", "notificationsEnabled": true },
    { "address": "0x4e2bC8463190fBa0C5bF5921a98552f4728E3e9f", "notificationsEnabled": true }
  ]
}
3

Send a notification

curl -X POST "https://www.base.dev/v1/notifications/send" \
  -H "x-api-key: $BASE_DEV_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "app_url": "https://your-app.com",
    "wallet_addresses": ["0xc0c3132DFc4929bb6F68FA76B8fB379fF8c5bE74"],
    "title": "Hey from your app!",
    "message": "You have a new reward waiting.",
    "target_path": "/rewards"
  }'
{
  "success": true,
  "results": [
    { "walletAddress": "0xc0c3132DFc4929bb6F68FA76B8fB379fF8c5bE74", "sent": true }
  ],
  "sentCount": 1,
  "failedCount": 0
}

Send from code

app/api/notify/route.ts
import { NextResponse } from "next/server";

const API_KEY = process.env.BASE_DEV_API_KEY!;
const APP_URL = process.env.NEXT_PUBLIC_URL!;

export async function POST(req: Request) {
  const { title, message, targetPath } = await req.json();

  const usersRes = await fetch(
    `https://www.base.dev/v1/notifications/app/users?app_url=${APP_URL}&notification_enabled=true`,
    { headers: { "x-api-key": API_KEY } }
  );
  const { users } = await usersRes.json();
  const addresses = users.map((u: { address: string }) => u.address);

  const sendRes = await fetch("https://www.base.dev/v1/notifications/send", {
    method: "POST",
    headers: { "x-api-key": API_KEY, "Content-Type": "application/json" },
    body: JSON.stringify({ app_url: APP_URL, wallet_addresses: addresses, title, message, target_path: targetPath }),
  });

  return NextResponse.json(await sendRes.json());
}

Scheduled notifications

Use Vercel Cron Jobs to send notifications on a recurring schedule.
app/api/cron/notify/route.ts
import { NextResponse } from "next/server";

const API_KEY = process.env.BASE_DEV_API_KEY!;
const APP_URL = process.env.NEXT_PUBLIC_URL!;

export async function GET(req: Request) {
  if (req.headers.get("Authorization") !== `Bearer ${process.env.CRON_SECRET}`) {
    return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
  }

  const usersRes = await fetch(
    `https://www.base.dev/v1/notifications/app/users?app_url=${APP_URL}&notification_enabled=true`,
    { headers: { "x-api-key": API_KEY } }
  );
  const { users } = await usersRes.json();
  const addresses = users.map((u: { address: string }) => u.address);

  const sendRes = await fetch("https://www.base.dev/v1/notifications/send", {
    method: "POST",
    headers: { "x-api-key": API_KEY, "Content-Type": "application/json" },
    body: JSON.stringify({
      app_url: APP_URL,
      wallet_addresses: addresses,
      title: "Daily reminder",
      message: "Check in today to keep your streak alive.",
      target_path: "/streak",
    }),
  });

  return NextResponse.json(await sendRes.json());
}
Register the schedule in vercel.json:
vercel.json
{
  "crons": [{ "path": "/api/cron/notify", "schedule": "0 9 * * *" }]
}

API reference

Both endpoints require your API key in the x-api-key header.

GET /v1/notifications/app/users

GET https://www.base.dev/v1/notifications/app/users
Query parameters
app_url
string
required
Your mini app URL as registered on Base.dev.
notification_enabled
boolean
Set to true to return only users who have enabled notifications.
Response
success
boolean
Whether the request succeeded.
users
array
List of user objects.

POST /v1/notifications/send

POST https://www.base.dev/v1/notifications/send
Request body
app_url
string
required
Your mini app URL as registered on Base.dev.
wallet_addresses
string[]
required
Wallet addresses to notify. Maximum 1,000 per request.
title
string
required
Notification title.
message
string
required
Notification body text.
target_path
string
Relative path to open when the user taps the notification (for example, /leaderboard). Omit to open your app at its root.
Response
success
boolean
Whether the request completed.
results
array
Per-address delivery status.
sentCount
number
Total notifications sent.
failedCount
number
Total notifications failed.
Errors
ErrorCause
InvalidArgumentMore than 1,000 wallet addresses in a single request.

Batching

Each request accepts up to 1,000 addresses. For larger audiences, split into chunks.
const BATCH_SIZE = 1000;

async function notifyAllUsers(allAddresses: string[], title: string, message: string, targetPath: string) {
  for (let i = 0; i < allAddresses.length; i += BATCH_SIZE) {
    const batch = allAddresses.slice(i, i + BATCH_SIZE);
    await fetch("https://www.base.dev/v1/notifications/send", {
      method: "POST",
      headers: { "x-api-key": process.env.BASE_DEV_API_KEY!, "Content-Type": "application/json" },
      body: JSON.stringify({ app_url: "https://your-app.com", wallet_addresses: batch, title, message, target_path: targetPath }),
    });
  }
}

Rate limits

ConstraintLimit
Requests per minute (per API key)10
Wallet addresses per request1,000
Max users notifiable per minute10,000