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.
Web push with your own VAPID keys
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
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.
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.
<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>
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.
importScripts('https://castly.sh/sdk/v1/sw.js');
One request with your secret key (sk_live_…). The API answers 202 right away and the queue delivers in the background.
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"
}'
What you get
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.
The API answers 202 instantly and Cloudflare Queues fans out in batches of 100. Your request never waits for browsers.
Every push leaves a trail: sent → delivered → clicked, or the push service error. Kept for 30 days.
Send to everyone, to your user IDs, to specific subscribers, to whoever has certain tags (such as plan=pro) or to your test devices.
Deliveries, clicks, completed sends and expired subscriptions, signed with HMAC-SHA256.
Design the soft prompt in the dashboard with a live preview: position, texts, colors, paths and frequency. Changes reach your site without a deploy.
A script from our CDN or an ESM module. It detects support, handles permissions and tells you when iOS needs the site installed.
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
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.
POST /v1/notifications
With your secret key and a target: everyone, users, subscribers or a segment.
202 · queued
Validates, checks your quota and queues it. Answers right away.
batches of 100
Pages through the recipients and signs every push with the project's private VAPID key.
FCM · Mozilla · Apple
Each subscriber's browser vendor. If it answers 404 or 410, the subscription is marked expired.
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
Every plan has the same features. Only the pushes per month and the projects change.
For trying things out and small projects.
US$0 forever
For products in production.
US$15 / month
For high volume and several products.
US$79 / month
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
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.
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.
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.
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.
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.
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.
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().
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.
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.
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.
Yes. Paid plans are billed monthly through Stripe and you cancel from Billing, in the dashboard, with no commitment.
Free plan forever with 10,000 pushes a month. No credit card.