Documentation/Integrations
Push Notifications: setup
Get a Firebase project, hand Engage the key, and start collecting device tokens from your site.
Push has two halves. Firebase does the delivering — you own the project and the keys. Engage decides who gets what and when. This page connects the two, then gets your site sending device tokens in.
Roughly 20 minutes, most of it in the Firebase console. You need someone who can add a file to your website and someone who can edit the Engage server config.
1. Create the Firebase project
Open the Firebase console
Go to console.firebase.google.com and click Add project. If you already have one for this brand, use it — one project per brand, not one per environment.
Add a Web app
In Project settings → General → Your apps, click the web icon (</>). Name it and register. Firebase shows a firebaseConfig object — copy it, your site needs it in step 4.
Generate a Web Push certificate
Project settings → Cloud Messaging → Web configuration → Web Push certificates → Generate key pair. This is the VAPID key. Your site needs it; Engage does not.
2. Get the service account JSON
This is the file Engage needs. It lets Engage send on your project's behalf.
Open Service accounts
Project settings → Service accounts → Firebase Admin SDK.
Generate a new private key
Click Generate new private key, then Generate key. A .json file downloads. This is the only copy — Firebase will not show it again.
That file is a credential with full send rights on your project. Do not commit it to a repository, paste it into a ticket, or email it. You upload it directly to Engage in the next step, so it never needs to travel anywhere else.
It should look like this — type must be service_account:
{
"type": "service_account",
"project_id": "your-project-id",
"private_key_id": "…",
"private_key": "-----BEGIN PRIVATE KEY-----\n…\n-----END PRIVATE KEY-----\n",
"client_email": "firebase-adminsdk-xxxxx@your-project-id.iam.gserviceaccount.com",
"client_id": "…",
"token_uri": "https://oauth2.googleapis.com/token"
}3. Upload it to Engage
No server config and no deploy. In Engage open Push Notifications, click the settings icon, and upload the .json file from step 2.
Open Firebase setup
Push Notifications → the settings icon in the top right. If push has never been set up, the banner on that screen links straight there.
Choose the file
Engage checks it before saving — first that it is a service account key at all, then by asking Google to issue a token with it. Expect a second's pause; that pause is the check.
Confirm what it says
Once connected you see the project, the service account address and the key ID. Those are enough to tell two keys apart, which is what you need when a rotation does not seem to have landed.
A key that has been deleted or revoked in the Firebase console is refused here, with Google's reason. That is deliberate: a revoked key parses perfectly, so without the live check it would save cleanly and fail during your first campaign.
The key is encrypted before it is stored and is never shown again — there is nothing to reveal, and no endpoint returns it. To rotate, generate a new key in Firebase and upload it over the old one; to stop sending, disconnect.
You need the settings permission to connect a project, so owners and admins can do this and editors cannot. An API key can never do it at all.
There is no environment to choose
Engage used to keep separate stage and prod Firebase credentials, and a send had to pick one. It does not any more: your uploaded project *is* the choice. If you want to keep test and live traffic apart, use two Engage accounts with a different Firebase project in each.
| Setting | Default | Who sets it |
|---|---|---|
| Firebase key | none | You, on the Firebase setup panel |
push_feature_enabled | true | An operator, to turn push off across the whole deployment |
push_provider | real FCM | An operator. mock simulates every send — it reports success and delivers nothing, so it belongs in local development and nowhere else |
push_token_event | push_notification | An operator, only if that event name is already used for something else |
Deployment-wide credentials in app.conf still work as a fallback for accounts that have not uploaded their own. They are operator-only and you should not need them — uploading takes precedence over anything configured there.
4. Add the service worker to your site
Web push needs a service worker at the root of your domain, so the file must be reachable at https://yoursite.com/firebase-messaging-sw.js — not in a subfolder. Put it in your public/ directory.
// public/firebase-messaging-sw.js
importScripts("https://www.gstatic.com/firebasejs/10.12.2/firebase-app-compat.js");
importScripts("https://www.gstatic.com/firebasejs/10.12.2/firebase-messaging-compat.js");
firebase.initializeApp({
apiKey: "…",
projectId: "your-project-id",
messagingSenderId: "…",
appId: "…",
});
firebase.messaging();Those values are the firebaseConfig from step 1. They are public by design — they identify your project, they do not authorise sending. The service account from step 2 is the secret, and it never goes near the browser.
5. Ask for permission and collect the token
On your site, request notification permission and read the device token. Ask at a moment that makes sense — after a login or a deliberate opt-in click. A prompt on first page load is the fastest way to get permanently blocked.
import { initializeApp } from "firebase/app";
import { getMessaging, getToken } from "firebase/messaging";
const app = initializeApp(firebaseConfig);
const messaging = getMessaging(app);
export async function enablePush(user) {
if (Notification.permission === "denied") return null;
if ((await Notification.requestPermission()) !== "granted") return null;
const token = await getToken(messaging, {
vapidKey: "YOUR_WEB_PUSH_CERTIFICATE_KEY",
});
if (!token) return null;
await sendTokenToEngage(user, token);
return token;
}6. Send the token to Engage
There is no push-specific endpoint to call. You send an ordinary event named push_notification carrying the token, and Engage mirrors it into the device table bound to that contact. That is the whole integration.
async function sendTokenToEngage(user, token) {
await fetch("https://api.engage.nevtan.com/api/v1/events", {
method: "POST",
headers: {
"Content-Type": "application/json",
"X-App-Id": "app_...", // Settings → Integrations → Setup
"X-App-Secret": "sk_live_...",
},
body: JSON.stringify({
event_type: "push_notification",
user: { email: user.email }, // required — this is the identity
properties: {
device_token: token,
platform: "desktop", // "ios" | "android" | "desktop"
},
}),
});
}Fire this on every launch, not just the first time. Device tokens rotate silently — the browser reissues them and never tells you the old one died. A site that registers once ends up with a list of addresses that quietly stop working.
| Field | Notes |
|---|---|
event_type | Must match push_token_event (default push_notification) |
user.email | Required. Contacts are identified by email; without it the event is rejected |
properties.device_token | token, push_token, fcm_token and registration_token are also accepted. Anything under 20 characters is ignored as a placeholder |
properties.platform | Send it explicitly. iPadOS reports a Mac user agent, so an iPad guessed from the user agent lands in desktop |
The endpoint always answers HTTP 200 — the outcome is in the body, so check success:
{ "success": true, "profile_id": "000000000000000000000001", "error": null }7. Send your first push
Check the device arrived
Open Push Notifications → Devices. Your browser should be listed as active. If it is not, the event did not reach Engage — check success in the response above.
Send to yourself
Open Compose, write a title and body, and use the device filter to target only your own device before sending to anyone else.
Then target a segment
Any segment works. "Subscribed to push in the last 30 days" is an ordinary segment condition, because the token arrived as an ordinary event.
When it does not work
| Symptom | Cause |
|---|---|
| `SENDER_ID_MISMATCH` on every device | Your devices were registered against a different Firebase project than the key you uploaded. Usually the site is still minting tokens with its old firebaseConfig — check the project id in firebase-messaging-sw.js against the one shown on the Firebase setup panel |
| Devices list stays empty | The event is not arriving. Check success in the response, that user.email is present, and that the event name matches push_token_event |
| Sends report success, nothing arrives | push_provider = mock is set. It simulates every send and contacts no device |
| Token rejected as too short | Under 20 characters — a placeholder or a truncated copy/paste |
| Devices go inactive over time | Normal. Google reports tokens as gone when someone clears site data or revokes permission, and Engage retires them so the list stays clean |
accepted in a send result means the push service took the message, not that a handset received it. No push platform reports actual delivery back, so treat that number as "handed over", not "delivered".
