Web push with your own VAPID keys

Push notifications for developers

Web and PWA push notifications with one API call. Every project has its own VAPID keys and you can export them anytime: no vendor lock-in.

Free plan forever · 10,000 pushes a month · No credit card

Start in minutes

Three steps and your site sends notifications.

No libraries to maintain and no keys to generate by hand. Create a project in the dashboard, copy its public key and you are set.

  1. 01

    Add the SDK

    Load the SDK and initialize it with the project's public key (pk_live_…, safe in the browser). Call subscribe() from a click: browsers only ask for permission after a user gesture.

    index.html
    <button id="notify">Notify me</button>
    
    <script src="https://castly.sh/sdk/v1/castly.js"></script>
    <script>
      Castly.init({ publicKey: 'pk_live_…' });
    
      document.querySelector('#notify').addEventListener('click', async () => {
        await Castly.subscribe({ userId: 'user_123', tags: { plan: 'pro' } });
      });
    </script>
  2. 02

    Create the service worker

    A /castly-sw.js file at the root of your site with a single line. If you already have a service worker, add the line to yours and pass its path as swPath.

    castly-sw.js
    importScripts('https://castly.sh/sdk/v1/sw.js');
  3. 03

    Send from your server

    One request with your secret key (sk_live_…). The API answers 202 right away and the queue delivers in the background.

    bash
    curl https://castly.sh/v1/notifications \
      -H "Authorization: Bearer $CASTLY_SECRET_KEY" \
      -H "Content-Type: application/json" \
      -d '{
        "to": { "type": "users", "userIds": ["user_123"] },
        "title": "Your order has shipped",
        "body": "Arriving today between 5 and 7 pm.",
        "url": "https://shop.example/orders/8421"
      }'

Full guide in the docs

What you get

What you need to send push for real, and nothing that ties you down.

Your own VAPID keys

One project per product, app or site, each with its own key pair; the private key is encrypted at rest. Export them anytime, together with your subscribers.

Queue-based delivery

The API answers 202 instantly and Cloudflare Queues fans out in batches of 100. Your request never waits for browsers.

Per-device logs

Every push leaves a trail: sent → delivered → clicked, or the push service error. Kept for 30 days.

Segments

Send to everyone, to your user IDs, to specific subscribers, to whoever has certain tags (such as plan=pro) or to your test devices.

Signed webhooks

Deliveries, clicks, completed sends and expired subscriptions, signed with HMAC-SHA256.

Opt-in prompt from the dashboard

Design the soft prompt in the dashboard with a live preview: position, texts, colors, paths and frequency. Changes reach your site without a deploy.

SDK under 3 KB

A script from our CDN or an ESM module. It detects support, handles permissions and tells you when iOS needs the site installed.

Edge-native

The API runs on Cloudflare Workers, close to whoever calls it, and fan-out on Cloudflare Queues. Your data stays on Cloudflare.

How it works

From your server to the device, without waiting.

Your request ends in milliseconds. Fan-out, signing and the encryption of each push happen afterwards, in queues.

Before sendingThe SDK subscribes each browser with your public VAPID key and registers it with your pk_live_… key.

  1. 01 Your server POST /v1/notifications

    With your secret key and a target: everyone, users, subscribers or a segment.

  2. 02 castly API 202 · queued

    Validates, checks your quota and queues it. Answers right away.

  3. 03 Cloudflare Queues batches of 100

    Pages through the recipients and signs every push with the project's private VAPID key.

  4. 04 Push service FCM · Mozilla · Apple

    Each subscriber's browser vendor. If it answers 404 or 410, the subscription is marked expired.

  5. 05 Device castly-sw.js

    Your service worker shows the notification and confirms delivery and the click.

On the way backEvery delivered and clicked lands in the device log and reaches you as a signed webhook.

Pricing

Start free. Pay when you send more.

Every plan has the same features. Only the pushes per month and the projects change.

Free

For trying things out and small projects.

US$0 forever

  • 10,000 pushes a month
  • 1 project
Start for free

Scale

For high volume and several products.

US$79 / month

  • 2,000,000 pushes a month
  • 25 projects
Choose Scale

Every plan includes

  • Unlimited subscribers
  • Your own VAPID keys, exportable
  • Signed webhooks
  • Per-device logs for 30 days
  • Opt-in prompt configured in the dashboard
  • Playground and test devices

Prices in US dollars, billed monthly through Stripe. Cancel anytime. The quota is per calendar month (UTC) and shared by every project in your organization.

FAQ

What people usually ask us.

Does it work on iPhone and iPad?

Yes, since iOS and iPadOS 16.4, with one condition from Apple: the person has to add your site to their home screen and open it from there, as a PWA (your site needs a manifest with display: standalone). A regular Safari tab on iPhone gets no push. Castly.needsInstall() tells you when to show install instructions.

Which browsers are supported?

Chrome, Edge, Firefox, Opera, Samsung Internet and other Chromium-based browsers, on desktop and Android; Safari 16 or later on macOS; and on iPhone and iPad, sites added to the home screen. Castly.isSupported() checks it in each browser.

Who owns the keys and the subscribers? Can I leave?

You do. GET /v1/project/export (or Export, in the dashboard) gives you the VAPID key pair, public and private, and every subscriber with its endpoint and keys. With that you can keep sending with any web push library, without asking your users to subscribe again.

What counts as a push?

One delivery attempt to one device. Sending a notification to 5,000 subscribers uses 5,000 pushes. Registering subscribers and read calls to the API do not use quota.

What happens when I reach my plan limit?

We email you when you pass 80%. At 100%, sending stops: the API answers 429 quota_exceeded until the next month starts (UTC) or you upgrade. Meanwhile, your subscribers keep registering as usual.

Is it GDPR compliant?

For your subscribers' data, your organization is the controller and castly is the processor: we only process it to deliver your notifications. We store the subscription endpoint and keys, the browser and OS, the user ID and tags if you send them, and delivery events, which are deleted after 30 days. Everything lives on Cloudflare. The data processing terms are in our terms.

Can I change the opt-in prompt without touching my site?

Yes. You set it up in the dashboard with a live preview: position, texts in Spanish and English, colors, delay, from which page view, on which paths and how many days to wait after "Not now". The SDK reads it on load, so changes reach your site within about 5 minutes, without a deploy. If you would rather decide when it shows, use autoPrompt: false and Castly.showPrompt().

Does it work for several products?

Yes: create one project per product, app or site (1, 5 or 25 depending on the plan). Each has its own VAPID keys, API keys, subscribers, webhook, opt-in prompt and test devices. The push quota belongs to the organization and is shared by all of them.

I already have a service worker. Do I need to change it?

You do not need two: add importScripts('…/sdk/v1/sw.js') at the top of yours and pass its path in Castly.init({ swPath }). It works with classic service workers, including those generated by Workbox.

How do I know a notification arrived?

Every notification has a per-device log: sent, delivered (the service worker received it) and clicked, or the error code from the push service. You see it in the dashboard, in GET /v1/notifications/:id and, if you set one up, by webhook.

Can I cancel anytime?

Yes. Paid plans are billed monthly through Stripe and you cancel from Billing, in the dashboard, with no commitment.

Send your first notification today.

Free plan forever with 10,000 pushes a month. No credit card.