# castly > castly (https://castly.sh) is a Web Push notification service for developers, by Fersoft Technologies, LLC: web and PWA push notifications with one API call, using the customer's own VAPID keys per project (exportable at any time, no vendor lock-in). Website and docs in Spanish and English. ## How it works - The customer creates one project per product, app or website in the dashboard (https://castly.sh/app). Each project has its own VAPID key pair (private key encrypted at rest), a public API key `pk_live_…` (safe in browsers) and secret API keys `sk_live_…` (server only), an optional webhook URL with a signing secret `whsec_…`, an opt-in prompt, test devices, a site URL (opened by pushes without `url`) and a default icon (used by pushes without `icon`). - Browser: load https://castly.sh/sdk/v1/castly.js (IIFE, `window.Castly`) or https://castly.sh/sdk/v1/castly.mjs (ESM), call `Castly.init({ publicKey })` and `await Castly.subscribe({ userId, tags })` from a click. Other methods: `isSupported()`, `needsInstall()` (iOS without the site on the home screen), `permission()`, `identify(userId, tags)`, `setTags(tags)`, `unsubscribe()`, `getSubscriberId()`, `on('subscribed' | 'unsubscribed' | 'error', cb)`. Errors are `CastlyError` with `code` unsupported, denied, not_initialized, not_subscribed, network or api. - Service worker: the customer creates `/castly-sw.js` at the site root containing `importScripts('https://castly.sh/sdk/v1/sw.js');`, or adds that line to an existing classic service worker and passes `swPath`. - Opt-in prompt configured in the dashboard (live preview): enabled, position (bottom-right, bottom-left, top, center), theme, accent color, icon, delay in seconds, from the Nth page view, days to wait after "not now", path prefixes and texts in Spanish and English. The SDK reads it from `GET /client-config` and shows it automatically after `init` (changes apply without a deploy, within about 5 minutes) when push is supported and permission is still "default". Opt out with `Castly.init({ publicKey, autoPrompt: false })`; show it yourself with `await Castly.showPrompt({ force })` ('accepted' | 'dismissed' | 'unavailable'). - Standalone soft prompt element: `` from https://castly.sh/sdk/v1/optin.js, configured with attributes (title, text, accept, dismiss, icon, accent, position, theme, delay, user-id, tags, remember-days) and CSS variables. - Test devices: subscribers marked in the dashboard (with a label); the browser subscribed from the Playground is marked automatically. Send to them with `{ "type": "test" }`. - Server: `POST https://castly.sh/v1/notifications` with `Authorization: Bearer sk_live_…` and `{ to, title, body?, url?, icon?, image?, data?, ttl?, urgency? }`. `to` is `{ "type": "all" }`, `{ "type": "users", "userIds": [...] }`, `{ "type": "subscribers", "subscriberIds": [...] }`, `{ "type": "segment", "tags": {...} }` or `{ "type": "test" }`. It answers 202 `{ id, status: "queued" }` immediately; Cloudflare Queues fan out and deliver in batches of 100. Expired endpoints (404/410) are marked expired automatically. - Delivery logs per device: sent → delivered → clicked (or failed/expired), kept 30 days. Webhooks: notification.delivered, notification.clicked, notification.completed, subscriber.expired, signed with `Castly-Signature: t=,v1=.">`. - Export: `GET https://castly.sh/v1/project/export` returns the VAPID key pair and every subscriber (endpoint and keys) to keep sending with any web-push library. ## REST API (base https://castly.sh/v1) - GET /client-config (pk or sk; returns projectId, vapidPublicKey and prompt or null) · POST /subscribers (pk or sk) · PATCH /subscribers/:id (pk or sk) · DELETE /subscribers/:id (pk or sk) - GET /subscribers (sk; filters status, userId, tag, test) · POST /notifications (sk, Idempotency-Key supported for 24 h) · GET /notifications (sk) · GET /notifications/:id (sk, with up to 500 delivery logs) - POST /events (no key, used by the service worker) · GET /project (sk) · GET /project/export (sk) - Errors: `{ "error": { "code", "message" } }`. 300 requests per minute per key (429 rate_limited); 429 quota_exceeded when the monthly pushes are used up; 403 org_suspended. ## Browsers - Chrome, Edge, Firefox, Opera, Samsung Internet and other Chromium browsers on desktop and Android; Safari 16+ on macOS; iPhone and iPad from iOS 16.4 only when the site is added to the home screen as a PWA. ## Plans (US$, billed monthly through Stripe) - Free: 10,000 pushes per month, 1 project, free forever. - Pro: 250,000 pushes per month, 5 projects, US$15/month. - Scale: 2,000,000 pushes per month, 25 projects, US$79/month. - Every plan: unlimited subscribers, webhooks, export and delivery logs for 30 days. A push is one delivery attempt to one device; the quota is per calendar month (UTC) across all projects. ## Not included - Native mobile app push (APNs/FCM in native apps), SMS, email campaigns, scheduled sends, A/B tests, team members (one person per organization in v1) and webhook retries. ## Pages - Home: https://castly.sh/ · https://castly.sh/en/ - Docs: https://castly.sh/docs · https://castly.sh/en/docs - Privacy: https://castly.sh/privacidad · https://castly.sh/en/privacy - Terms: https://castly.sh/terminos · https://castly.sh/en/terms - Blog (web push for developers and product teams): https://castly.sh/blog · https://castly.sh/en/blog Operated by Fersoft Technologies, LLC. Contact: hola@castly.sh