Coming Soon

Push notifications for iOS, Android, and web.

Token-management abstraction, per-device opt-in, native rich payloads, segment targeting. Same auth, same idempotency, same webhooks as every other Bird channel — because the same engineering team built them all.

push.ts
200 · 0.2s
import { BirdClient } from "@bird/sdk";

const bird = new BirdClient({ apiKey: process.env.BIRD_API_KEY! });

// Send by device token...
const { data, error } = await bird.push.send({
  to:    "token_aPa91Hb...XzQ",
  title: "Your driver is two minutes away",
  body:  "Tap to see the live map.",
  data:  { ride_id: "ride_2891" },
}).safe();

// ...or by user_id — we fan out to every opted-in device.
await bird.push.send({
  to:    { user_id: "usr_4hQ2m" },
  title: "Your code is 482917",
  body:  "Enter it on the login screen.",
  data:  { kind: "otp" },
});

if (error) throw error;
console.log(data.id);
// → "push_2nQ81oP3..."

5 minutes from npm install to first send

Send a notification from the language you already use.

SDKs in every major runtime. Register a token from your mobile or web client; send by token or by user_id from your backend.

1
2
3
4
5
6
7
8
9
10
import { BirdClient } from "@bird/sdk";

const bird = new BirdClient({ apiKey: process.env.BIRD_API_KEY! });

const { data, error } = await bird.push.send({
  to:    "token_aPa91Hb",
  title: "Hello",
  body:  "From Bird Push",
  data:  { kind: "demo" },
}).safe();

Ten primitives that turn APNs and FCM into one endpoint.

Real APNs and FCM under the hood — no third-party push aggregator in the middle. We just hide the platform quirks behind one shape.

  1. 01

    iOS, Android, and web from one call

    One bird.push.send. We fan out to APNs, FCM, and the W3C Push Protocol per registered device.

  2. 02

    Token-management abstraction

    Register a token with bird.push.tokens.register. We track platform, locale, and last-seen so you don't have to.

  3. 03

    Send by token or by user_id

    Address a single device, or pass { user_id } and we fan out to every opted-in device they own.

  4. 04

    Per-device opt-in state

    Each registered token carries its own opt-in state. Revoke one without affecting the others.

  5. 05

    Native rich payloads

    Title, body, image, action buttons, deep-link URL, custom data — mapped to APNs alert and FCM notification correctly.

  6. 06

    Silent / background notifications

    Pass content_available: true and we route through APNs background and FCM data-only correctly per platform.

  7. 07

    Segment targeting

    Tag users at registration time; address by tag from bird.push.send. No third-party CDP integration required.

  8. 08

    Delivery and open receipts

    Events for push.delivered, push.opened, push.dismissed. Same HMAC envelope as every other channel.

  9. 09

    Token lifecycle handled

    When APNs / FCM returns an unregistered token, we mark it dead and stop sending. No manual cleanup.

  10. 10

    Same auth, same error envelope

    One API key for Push, SMS, Email, WhatsApp, Voice. One error type registry across all of them.

Why we build Push

Because push shouldn't live in a different vendor than your SMS, your email, and your verifications.

Every product team ends up with the same stack: APNs for iOS, FCM for Android, a service worker for web, and a database table tracking which user has which tokens with which opt-in state. We built that table, then we built it again, then we exposed it as bird.push.tokens.register and bird.push.send. Same auth, same idempotency, same error envelope as Email and SMS — so the OTP that fails on push falls back to SMS in the same request.

push.ts
200 · 0.2s
import { BirdClient } from "@bird/sdk";

const bird = new BirdClient({ apiKey: process.env.BIRD_API_KEY! });

// Send by device token...
const { data, error } = await bird.push.send({
  to:    "token_aPa91Hb...XzQ",
  title: "Your driver is two minutes away",
  body:  "Tap to see the live map.",
  data:  { ride_id: "ride_2891" },
}).safe();

// ...or by user_id — we fan out to every opted-in device.
await bird.push.send({
  to:    { user_id: "usr_4hQ2m" },
  title: "Your code is 482917",
  body:  "Enter it on the login screen.",
  data:  { kind: "otp" },
});

if (error) throw error;
console.log(data.id);
// → "push_2nQ81oP3..."

Every state change is a webhook.

HMAC-signed payloads, replay-protected, idempotent. The same envelope on every Bird channel — learn one, you've learned them all.

POST /webhooks/bird
signed
{
  "type": "push.delivered",
  "id":   "evt_9pT81y...",
  "created_at": "2026-05-19T15:42:01.221Z",
  "data": {
    "push_id":   "push_2nQ81oP3",
    "user_id":   "usr_4hQ2m",
    "platform":  "ios",
    "device":    "iPhone15,2",
    "latency_ms": 218
  }
}

Retry schedule: 5s, 30s, 5m, 30m, 2h, 6h, 12h. Dead-letter after the final attempt; every dead-lettered event is replayable from the dashboard or API.

  • push.queuedAccepted by the API and queued for send.
  • push.sentHanded off to APNs / FCM / W3C Push server.
  • push.deliveredProvider confirmed delivery to the device (when available — APNs is best-effort).
  • push.openedUser tapped the notification (if your app instruments it).
  • push.dismissedUser dismissed without opening (if your app instruments it).
  • push.failedPermanent failure (token unregistered, app removed). The token is auto-disabled.

If you've integrated SMS, you've integrated Push.

Same auth, same idempotency contract, same error envelope, same webhook shape. The difference is the transport — not how you call it.

Push.

push
await bird.push.send({
  to:    { user_id: "usr_4hQ2m" },
  title: "Your code",
  body:  `Code: ${code}`,
});

One call. We fan out to every opted-in device the user owns, across APNs, FCM, and the web.

SMS.

sms
await bird.sms.send({
  from: "Bird",
  to:   "+15005550006",
  text: `Your code is ${code}.`,
});

Same envelope, same idempotency contract. Fall back when push fails — or address SMS directly.

$0.0005 per notification, every platform.

Priced per delivered notification. Same rate on iOS, Android, and web. APNs and FCM are free upstream, so we charge for the routing and the dashboard, not the transport. Volume discounts auto-apply above 10M/mo.

Start with one channel.
Add the others when you're ready.

A test API key is yours immediately. Production unlocks when you add a payment method and verify a sender.

Get startedRead docsor

Using Claude Code, Cursor, or Codex? Point it at our MCP server — tools for every channel we expose, with scoped agent keys.

Cursor