Install Pusharoo on any website
Two files, one line of HTML. Works on any site where you can put a file at the root of your domain and add a script tag — plain HTML, PHP, WordPress, Laravel, Next.js, you name it.
https://yoursite.com/sw.js, and a site key from the Pusharoo dashboard — create a free account to get one (it takes a minute).1. Add your site and get a site key
In the dashboard go to Websites → Add website. Enter the exact address visitors use, e.g. https://yourstore.com (the www. version is accepted automatically). Add your logo URL so your prompt and notifications carry your brand — Pusharoo never shows its own name to your visitors.
You’ll get a public site key like pk_3f9a…. It’s safe to put in your HTML: it only works from the website address you registered (requests from any other origin are refused).
2. Upload sw.js to your site root
Browsers only deliver notifications to a service worker that lives on your domain. Download ours and upload it so it opens at https://yoursite.com/sw.js:
Or from a terminal on your server:
curl -o /path/to/your/webroot/sw.js https://pusharoo.com/sw.jsServe it as JavaScript and don’t let it be cached for long. On Apache, add this to your site’s .htaccess:
<Files "sw.js">
Header set Cache-Control "no-store, max-age=0"
Header set Service-Worker-Allowed "/"
</Files>3. Add the snippet
Paste this before </head> on every page (or in your site’s global header/footer):
<script src="https://pusharoo.com/sdk/pusharoo.js" data-site="pk_your_site_key" async></script>That’s it. After a few seconds a polite, branded prompt appears. The browser’s own permission dialog is only shown after the visitor taps Allow on yours — never on page load. A visitor who taps “Not now” isn’t asked again for a few days, and a browser that says “Block” is never asked again.
A fuller example with timing options:
<script src="https://pusharoo.com/sdk/pusharoo.js"
data-site="pk_your_site_key"
data-delay="8000" data-scroll="30"
data-title="Want first dibs on deals?"
data-text="We send 1–2 alerts a week. Unsubscribe anytime."
data-ios-hint="true" async></script>4. iPhone & iPad (iOS / iPadOS 16.4+)
Apple only allows web push from sites that have been added to the Home Screen. A regular Safari or Chrome tab on iPhone can’t subscribe. The visitor’s steps are:
- Tap Share → Add to Home Screen.
- Open your site from the new Home Screen icon.
- Tap Allow on your prompt, then on the iOS dialog.
For this to work your site needs a small web app manifest. Save this as /manifest.webmanifest (or /manifest.json) at your site root, with your own name and icons:
{
"name": "Your Store",
"short_name": "Your Store",
"id": "/",
"start_url": "/",
"scope": "/",
"display": "standalone",
"background_color": "#ffffff",
"theme_color": "#ffffff",
"icons": [
{ "src": "/icon-192.png", "sizes": "192x192", "type": "image/png" },
{ "src": "/icon-512.png", "sizes": "512x512", "type": "image/png" }
]
}…and link it from your <head>:
<link rel="manifest" href="/manifest.webmanifest">
<link rel="apple-touch-icon" href="/icon-192.png">
<meta name="apple-mobile-web-app-capable" content="yes">Set data-ios-hint="true" on the snippet to show iPhone visitors a friendly how-to card automatically.
Prompt options
| Attribute | Default | What it does |
|---|---|---|
data-site | required | Your site key from the dashboard (pk_…). |
data-delay | 6000 | Milliseconds to wait before showing the soft prompt. |
data-scroll | 0 | Also wait until the visitor has scrolled this % of the page (0 = off). |
data-cap-days | 3 | After “Not now”, wait this many days before asking again. |
data-max-dismiss | 3 | Stop asking automatically after this many “Not now” taps. |
data-auto | true | Set to false to never prompt automatically — only from your own button via Pusharoo.subscribe(). |
data-title | “Get updates from {Your site}?” | Prompt headline. |
data-text | friendly default | Prompt body text (or set it per site in the dashboard). |
data-allow / data-later | Allow / Not now | Button labels. |
data-position | auto | auto (top-right on desktop, bottom sheet on phones), top or bottom. |
data-ios-hint | false | true shows iPhone visitors a short “Add to Home Screen” how-to card. |
data-sw | /sw.js | Path of your service worker if it isn’t /sw.js. |
data-debug | false | Log what the snippet is doing to the console. |
Already have a service worker?
A page can only have one service worker per scope, so don’t register a second one. Instead add these two lines at the top of your existing worker and point the snippet at it:
self.PUSHAROO_SITE = 'pk_your_site_key';
importScripts('https://pusharoo.com/sw.js');<script src="https://pusharoo.com/sdk/pusharoo.js" data-site="pk_your_site_key" data-sw="/your-existing-sw.js" async></script>Cloudflare & caching
If your site is behind Cloudflare (or any CDN), make sure /sw.js and your manifest are never cached at the edge, so updates reach visitors right away. In Cloudflare: Caching → Cache Rules → Create rule:
- If: URI Path equals
/sw.jsor URI Path equals/manifest.webmanifest - Then: Cache eligibility → Bypass cache
Also make sure Rocket Loader or other script optimizers don’t rewrite sw.js. The snippet registers the worker with updateViaCache: 'none', so the browser always checks for a fresh copy.
JavaScript API & events
The snippet exposes window.Pusharoo so you can build your own “Get alerts” button or bell:
<button onclick="Pusharoo.subscribe()">🔔 Get deal alerts</button>
Pusharoo.state(); // "default" | "subscribed" | "granted" | "denied" | "unsupported" | "ios-needs-home-screen"
Pusharoo.subscribe(); // shows your soft prompt now (ignores the frequency cap)
Pusharoo.prompt(); // shows the prompt only if the frequency cap allows
Pusharoo.unsubscribe(); // unsubscribes this browserAnd it fires browser events you can hook into (analytics, UI, your CRM):
window.addEventListener('pusharoo:prompt-shown', () => {});
window.addEventListener('pusharoo:subscribed', e => console.log(e.detail.endpoint, e.detail.isNew));
window.addEventListener('pusharoo:dismissed', e => {});
window.addEventListener('pusharoo:permission', e => console.log(e.detail.permission));
window.addEventListener('pusharoo:unsubscribed', () => {});Tags & events
Tell Pusharoo about a subscriber so you can segment and automate on it. Calls made before someone subscribes are queued in the browser and sent when they do.
Pusharoo.tag('plan', 'pro'); // one tag
Pusharoo.tag({ plan: 'pro', city: 'Toronto' }); // several at once (up to 30)
Pusharoo.untag('plan'); // remove
Pusharoo.track('added_to_cart'); // a custom event (letters, numbers, _ . - :)
Pusharoo.track('purchase');Use them in Segments (“Tag” and “Did event”), as automation triggers (“A custom event happens”, “A tag is set”), and as a stop condition (“Stop if event: purchase” cancels a waiting cart reminder). Your server can send the same tags and events with the REST API.
Webhooks
Add an HTTPS webhook URL to any website in the dashboard and Pusharoo will POST JSON to it for subscribe, unsubscribe, click (with variant and button) and custom event events. Works with Zapier and Make: paste a “Catch Hook” URL as the webhook. Failed deliveries are retried with backoff (up to 6 attempts).
POST https://your-server.com/pusharoo-hook
X-Pusharoo-Event: subscribe
X-Pusharoo-Timestamp: 1794470000
X-Pusharoo-Signature: sha256=<hex HMAC>
{"event":"subscribe","site":{"id":1,"name":"Your Store","origin":"https://yourstore.com"},
"data":{"subscription_id":42,"platform":"android","locale":"en-CA","timezone":"America/Toronto"},
"ts":"2026-11-12T11:00:00+00:00"}Verify the signature with the webhook secret shown in the dashboard:
<?php
$raw = file_get_contents('php://input');
$ts = $_SERVER['HTTP_X_PUSHAROO_TIMESTAMP'] ?? '';
$sig = $_SERVER['HTTP_X_PUSHAROO_SIGNATURE'] ?? '';
$ok = hash_equals('sha256=' . hash_hmac('sha256', $ts . '.' . $raw, getenv('PUSHAROO_WEBHOOK_SECRET')), $sig)
&& abs(time() - (int)$ts) < 300;Segments, schedules & automations
- Segments — combine website, device (desktop, Android, iPhone/iPad), seen in the last N days, inactive for N+ days, joined in the last N days, language, timezone (e.g.
Europe/), tag (key or key=value), did a custom event, clicked in the last N days, or never clicked. Reach is counted live; save segments to reuse them. - Schedules — send now, pick a date and time, or repeat every day, week or month (optionally until a date). Each repeat is its own campaign with its own stats; “Stop repeating” ends the series.
- Drafts — “Save draft” keeps a campaign for later; RSS feeds can create drafts for you to review.
- Reliable sending — campaigns send in the background and save progress after every batch. If the sender is interrupted, it picks up where it stopped without sending anyone a duplicate. “Stop” halts a send mid-way; “Send again” resumes it.
- Automations — trigger on: someone subscribes, a custom event, a tag being set, a click on any notification, or a subscriber going quiet for N days (re-engagement). Add a wait (minutes, hours, days) and an optional “stop if event” — e.g. added_to_cart → wait 1 hour → “You left something behind”, stop if purchase.
- Stats — every campaign and automation shows targeted, delivered, expired (cleaned up), clicks and CTR; A/B tests show both variants side by side.
A/B tests & action buttons
A/B tests: in a new campaign, write a variant B title, message or image, then choose 50/50 split (everyone gets A or B) or Test on a slice, then send the winner (10–50% get A or B; after the wait you choose, the variant with the higher click-through rate goes to everyone else automatically). Nobody receives both.
Action buttons: add up to two buttons, each with its own link (e.g. “Shop now” and “See details”). Chrome, Edge and Android show them; other browsers show the notification without buttons and the main link still works. Button clicks are counted separately.
RSS-to-push
Dashboard → Integrations → Add a feed. Paste any RSS or Atom feed (WordPress /feed/, Ghost, Substack, Shopify blog .atom, YouTube channel feeds), pick the website and, optionally, a tag/event audience. New posts become notifications automatically — or drafts for you to review. Title = post title, message = summary, image = featured image, click = the post. Only posts published after you add the feed are sent, at most one per check, so a backlog never floods anyone.
REST API
Create a key in Integrations → REST API. It’s shown once and stored hashed. The API is server-to-server only (requests with a browser Origin are refused), limited to 120 requests a minute per key, and only sees your own websites.
curl https://pusharoo.com/api/v1/campaigns \
-H "Authorization: Bearer $PUSHAROO_KEY" -H "Content-Type: application/json" \
-d '{"title":"Back in stock","body":"Your size is here","url":"https://yourstore.com/p/1",
"site_id":1,"filters":{"tag_key":"wishlist","tag_value":"p1"},
"buttons":[{"label":"Buy now","url":"https://yourstore.com/cart/add/1"}],
"send_at":"2026-12-01T09:00:00-05:00"}'| Method & path | What it does |
|---|---|
GET /api/v1/me | Check the key |
GET /api/v1/sites | Your websites, site keys and active subscriber counts |
GET /api/v1/segments | Saved segments with live counts |
POST /api/v1/audience | Count an audience: site_id, segment_id, filters |
POST /api/v1/campaigns | Send now, schedule (send_at), or "draft":true. Optional icon, image, buttons, segment_id, filters, subscriber_ids, repeat (daily/weekly/monthly), ab {mode: split|winner, title, body, image, test_pct, wait_minutes} |
GET /api/v1/campaigns, GET /api/v1/campaigns/ | Status and stats (incl. A/B results) |
POST /api/v1/campaigns/ · /cancel | Send a draft (or resume) · cancel / stop |
GET /api/v1/subscribers | Paginate with after + limit (≤500); filter with site_id, tag_key, tag_value, event… |
POST /api/v1/subscribers/ | {"tags":{"plan":"pro","trial":null}} — null removes |
POST /api/v1/events | {"subscriber_id":42,"event":"purchase"} — fires event automations and stop conditions |
POST /api/v1/notify | Transactional: send immediately to up to 1,000 subscriber_ids (“your order shipped”) |
GET /api/v1/stats | Active subscribers, new this week, delivered and clicks in 30 days |
Get a subscriber’s id from the pusharoo:subscribed browser event’s endpoint via your webhook (subscribe carries subscription_id), then store it on your user record.
Google Tag Manager
Create a Custom HTML tag with the snippet, trigger All Pages, publish. You still need /sw.js at your site root (download the two-line version from your website page in the dashboard). Tag and track from other tags with Pusharoo.tag() / Pusharoo.track().
WordPress
- Upload
sw.jsto the folder that containswp-config.php(your site root) with your host’s File Manager or SFTP. Check thathttps://yoursite.com/sw.jsopens. - Add the snippet to your header: in a block theme use Appearance → Editor → Patterns/Template parts → Header with a Custom HTML block, or use any “insert headers and footers” plugin, or add it to your child theme’s
header.phpbefore</head>. - Caching plugins (WP Rocket, LiteSpeed Cache, W3TC): exclude
/sw.jsfrom caching and from JS minify/combine, and excludepusharoo.jsfrom “delay JavaScript”.
Shopify & hosted site builders
yourstore.com/sw.js. Service workers must be served from your own domain, so the standard install can’t work there as-is.Options that do work:
- Cloudflare Worker (recommended) — if your store’s domain is proxied by Cloudflare (orange cloud), open your website in the Pusharoo dashboard and download Cloudflare Worker. In Cloudflare: Workers & Pages → Create → Worker, paste the file, deploy, then add the route
yourstore.com/sw.js*. The worker answers only/sw.js(with your site key built in) and passes everything else straight through. Then paste the snippet in Online Store → Themes → Edit code → theme.liquid before</head>and run Check my installation. - Shopify App Proxy — apps can serve content under
/apps/…, but that limits the worker’s scope to/apps/…pages, so it isn’t a full-site solution. - Collect on a site you control — e.g. a blog or landing page on your own server, and link to the store from your notifications.
Test checklist
https://yoursite.com/sw.jsopens and shows JavaScript.- Open your site in Chrome desktop, wait for the prompt, tap Allow twice.
- In the dashboard, your website’s subscriber count goes up by one.
- Send a campaign (or use “Send test” on a subscriber) and watch it arrive.
- On Android Chrome, repeat. On iPhone, Add to Home Screen first.
Troubleshooting
- No prompt? You may have tapped “Not now” recently or blocked notifications. Try a private window, or run
localStorage.clear()in the console, or reset the site’s notification permission in browser settings. Adddata-debug="true"to see logs. - “origin not allowed” — the website address in the dashboard must exactly match the page’s origin (https, same domain).
- Subscribed but nothing arrives — check Focus/Do Not Disturb, OS notification settings for the browser, and that
sw.jsisn’t cached or blocked. - iPhone shows nothing — it must be opened from the Home Screen icon on iOS 16.4+.
Privacy & unsubscribing
Pusharoo stores only the browser’s push address and keys, browser type, language and timezone — no names, emails or cookies. Visitors can unsubscribe from the browser’s site settings at any time, or via your own button using Pusharoo.unsubscribe(). Addresses that browsers report as gone (HTTP 404/410) are removed from sending immediately and purged automatically. See our privacy note.
Uninstall
Remove the script tag. If you also want existing subscribers’ browsers to drop the worker, replace /sw.js with this for a few weeks before deleting it:
self.addEventListener('install', () => self.skipWaiting());
self.addEventListener('activate', () => self.registration.unregister());