Installing Bildirim on your site is a job of two files: a script tag in <head> and a service worker file on your own domain. This page walks through the install platform by platform, then shows the full surface of the browser SDK.
#The three parts of an install
Whichever platform you use, the install has the same three parts:
- The script tag — inside
<head>, on every page. - The service worker file — in your site's
/bildirim/folder. This is what delivers notifications while the page is closed. - A manifest — only if you want to send to iPhone/iPad.
<link rel="preconnect" href="https://cdn.bildirim.io" crossorigin>
<script src="https://cdn.bildirim.io/sdk.js"
data-key="pk_your_key"
data-api="https://api.bildirim.io"
defer></script>
Download BildirimSDK-sw.js from the Install screen in the panel and put it in a bildirim folder in your site's root. The address must be exactly:
https://yoursite.com/bildirim/BildirimSDK-sw.js
data-apiis required. Without it the SDK derives the API address from the script's own origin and callscdn.bildirim.io/v1/.... That host does not serve the API; subscriptions fail without showing an error.
A service worker cannot be loaded from a CDN. Browsers require a service worker to be served from the site's own origin. That is why you host that one file; its contents are a three-line bridge that calls our worker, you upload it once and never need to change it.
Why the /bildirim/ folder and not the root? A service worker's scope can be owned by only one worker at a time. If the file sat in your site's root and you already had a service worker (a PWA, an offline cache), ours would replace it and break that feature. In its own folder the two run side by side without trouble.
#WordPress
The official plugin does all three steps; you do not need to paste any code. The plugin is published in the WordPress plugin directory.
- Search for Bildirim under Plugins → Add New.
- Install Now → Activate.
- Paste the
pk_key from the panel into Settings → Bildirim.
This is the recommended route: updates arrive automatically through WordPress.
If you want a Turkish interface, download the zip and install it with Plugins → Add New → Upload Plugin. The directory build is in English: wordpress.org does not accept translation files shipped inside the package, translations are distributed through translate.wordpress.org, and Turkish has not been approved there yet. There is no functional difference, only the interface language — but installing from the zip means updating by hand.
The key is validated before it is saved: a wrong key is not accepted, because a wrong key makes the install look like it works while collecting no subscribers at all.
What the plugin handles:
- Prints the script tag inside
<head>with the right attributes. - Writes the service worker file to disk; if the filesystem is not writable it serves the file through WordPress instead. The settings page tells you which one is in effect and whether the address really works.
- Adds exclusion attributes and filters for LiteSpeed Cache, WP Rocket and Autoptimize.
- Serves a
manifest.jsonif you enable iOS support. If your site already has a manifest it leaves it alone — overwriting it would break your PWA settings, which is why the option is off by default.
When you remove the plugin, only the file it wrote itself is deleted: if you edited the service worker file yourself, it stays.
If you cannot install plugins (some managed hosts), the manual route applies: in Appearance → Theme File Editor → header.php, add the script just before the </head> line, and upload the service worker file to the /bildirim/ folder over FTP. If you have a JS-combining plugin, exclude the script in its settings.
#Shopify
- Open Online Store → Themes → Edit code → theme.liquid.
- Add the script tag before the
</head>line. - Upload
BildirimSDK-sw.jsthrough Settings → Files.
Shopify serves files from its own CDN address, so the file does not appear on your domain under /bildirim/. In that case the service worker step needs access to your store's theme files; without that access, a web push install cannot be completed on Shopify — write to our support team and we will look at your store's setup.
#Next.js, Nuxt and similar frameworks
Put the script in your app's root layout and drop the service worker file into public/bildirim/ — it is the static file folder, so it is served from the right address.
A Next.js App Router example:
// app/layout.jsx
export default function RootLayout({ children }) {
return (
<html lang="en">
<head>
<link rel="preconnect" href="https://cdn.bildirim.io" crossOrigin="" />
<script
src="https://cdn.bildirim.io/sdk.js"
data-key="pk_your_key"
data-api="https://api.bildirim.io"
defer
/>
</head>
<body>{children}</body>
</html>
);
}
File placement: public/bildirim/BildirimSDK-sw.js → https://yoursite.com/bildirim/BildirimSDK-sw.js.
#Google Tag Manager
Installing through GTM works but is not recommended: GTM injects the script after the page has loaded, so the permission prompt appears late, and when an ad blocker cuts GTM your install silently disappears. If you do use it, add the script as a Custom HTML tag; you cannot upload the service worker file through GTM, so that still has to go on your server.
#Verifying the install
The Install → Check install button in the panel fetches your site server-side and checks it. The results fall into two groups:
- Required checks (
APP_ID,SDK,SERVICE_WORKER) — live sending stays closed until all three pass. - Recommendations — Cloudflare detection, the manifest, the worker version, a conflicting service worker. These do not block sending.
Passing the code check is not enough: the proof that it really works is the first subscriber. The panel waits for that first subscriber live and shows it when it arrives.
What each check code means is on the Troubleshooting page.
#Script tag attributes
| Attribute | Required | Default | What it does |
|---|---|---|---|
data-key |
Yes | — | Your project's public key (pk_…). Without it the SDK does nothing. |
data-api |
Yes | the script's origin | The API address. Without it cdn.bildirim.io is assumed and subscriptions do not work. |
data-sw |
No | /bildirim/BildirimSDK-sw.js |
The address of the service worker file. |
data-scope |
No | /bildirim/ |
The worker's scope. Must stay within the data-sw path. |
data-auto-prompt |
No | on | Set "false" and the permission prompt never appears by itself; you open it with Bildirim.subscribe(). |
#The permission prompt
The prompt's style and wording are configured in the panel (Settings → Permission prompt), not in code. There are three styles:
- native — the browser's own permission dialog, with nothing in between.
- bell — a bell button in the corner; clicking it asks for permission.
- banner — a strip at the top of the page.
Configurable fields: delay (seconds), title, text, accept/decline button labels, colour and position (four corners or centre).
Browsers only allow the permission prompt after a user interaction. That is why bell and banner exist: they produce an interaction themselves and then ask. If you choose native directly, we have to wait for the first click.
#SDK reference
The SDK lives at window.Bildirim. Because it loads with defer, your own code may run before it; queue your calls:
<script>
window.BildirimDeferred = window.BildirimDeferred || [];
BildirimDeferred.push(async (pb) => {
await pb.setTags({ plan: 'pro', city: 'istanbul' });
});
</script>
The same line works after the SDK has loaded too — you do not need to know which moment you are in.
| Call | What it does |
|---|---|
Bildirim.subscribe() |
Asks for permission and records the subscription. Returns { ok, status, subscriberId }. |
Bildirim.unsubscribe() |
Removes the subscription. |
Bildirim.setTags({ ... }) |
Writes tags onto the subscriber. A null value deletes the tag. Segments are built on tags. |
Bildirim.track('purchase', { value: 249.9, currency: 'TRY' }) |
Reports a conversion event; notification revenue is computed from it. |
Bildirim.login('user-123', signature?) |
Matches the subscriber to your own user id. If identity verification is on for the project, the second parameter is required: HMAC-SHA256(identity secret, externalId) in hex, generated on your server. The secret is under Settings → Keys → Identity verification. |
Bildirim.logout() |
Removes the match (the shared-device case). |
Bildirim.showTopics() |
Opens the topic preference card. |
Bildirim.showIosInstall() |
Opens the "Add to Home Screen" card in iOS Safari. |
Bildirim.isSupported() |
Tells you whether the browser supports web push. |
The status in the result of subscribe() is one of: subscribed, denied (the user declined), unsupported (the browser does not support it), error.
#Tags and segments
A tag is a key–value pair you write onto a subscriber record. Segments in the panel are built on these tags, so for a target like "pro users in Istanbul" you have to write the tag first.
BildirimDeferred.push((pb) => pb.setTags({ category: 'sport', member: true }));
#Topic subscriptions
If you define topics in the panel (Settings → Topics), a Bildirim.showTopics() call shows the visitor a selection card. The choice becomes a tag in the form topic_<key> and can be used in segments. You can wire the card to your own button:
<button onclick="Bildirim.showTopics()">My notification preferences</button>
#Event tracking and revenue
A track() call writes a conversion event. A purchase that follows a notification click is attributed as "notification revenue" in the panel (a last-click model).
Bildirim.track('purchase', { value: 249.9, currency: 'TRY' });
While offline, the SDK queues events locally and sends them when the connection returns.
#How the notification looks: icon and sound
#Where the icon comes from
The small icon shown on the notification is picked from a three-step ladder:
- The notification's own icon — the "Icon" field on the campaign screen,
or the
iconfield in the API. - The project's default icon — Settings → General → "Default icon URL".
When you verify your install and this is empty, your site's icon
(
apple-touch-icon, falling back to the favicon) is written here automatically. - Your site's
/favicon.ico— if neither exists, the service worker falls back to that address in the visitor's browser. If the file is not there, the notification is shown without an icon.
So even with nothing configured, your notifications carry your site's mark. If you want to supply your own, a 192×192 square PNG is recommended: a non-square logo is cropped and anything under 96×96 looks blurry on a retina screen.
#Sound
A browser notification cannot carry its own sound. The Notification API's
sound field was dropped from the spec and no browser implements it; when the
notification is shown, the operating system's own notification sound plays and
a website cannot change it. If you see a provider promising otherwise, they are
probably describing what follows.
What is possible: while your site is open in the visitor's browser, the page plays the sound. When a notification arrives the service worker tells the open tab, the page plays a short tone (a rising A5→E6 fifth, generated with WebAudio — no audio file to download) and the notification is then shown silent, so the visitor does not hear two sounds. If no tab is open, or the browser has not unlocked audio yet, nothing is silenced: the OS sound plays.
The setting lives under Settings → General → Notification sound and is on by default; the "Play the sound" button next to it lets you hear the tone.
Browsers only release audio after the user has interacted with the page once. If the visitor has not clicked anywhere, the tone does not play — that is the autoplay policy, not a fault.
Mobile apps work differently: iOS takes the notification sound from the APNs payload (the default system sound is sent) and on Android the sound is a property of the notification channel, configured in your app.
#iOS (iPhone / iPad)
iOS 16.4 and later support web push, but only if the user has added your site to the Home Screen. There is no notification API in a normal Safari tab; that is the browser's rule, not something about your install.
For it to work, your site has to be declared as an installable app — that is, a manifest containing display: standalone has to be live. You can download the manifest from the Install screen in the panel; if your site already has a manifest.json, do not overwrite it — just add the line "gcm_sender_id": "103953800507" inside it.
When the manifest is live, the SDK shows iOS Safari visitors who have not yet added the site a card explaining the steps. The card only appears if the manifest exists — so that we never describe a feature that is not there. If the user chooses "Got it", the card stays hidden for seven days.
#Browser support
Web push works in Chrome, Edge, Firefox, Opera and Safari (macOS 13+, iOS 16.4+ with the home-screen requirement). HTTPS is required — localhost is the exception for development.
#Plan limits
| Plan | Active subscribers | Monthly sends |
|---|---|---|
| Starter (Free) | 10,000 | 3,000,000 |
| Pro | 50,000 | 15,000,000 |
| Publisher | 120,000 | 36,000,000 |
| Business | 250,000 | 75,000,000 |
On every plan the monthly send quota is computed as the subscriber ceiling × 10 notifications per day × 30 days. Starter is free forever; notifications carry a "Sent with Bildirim" badge. Enterprise limits are agreed in a quote.
When a limit is reached sending stops; no overage fee is charged. When the subscriber limit is reached no new subscribers are taken, but sending to existing ones continues.