sethserver.com Subscribe
Two cartoon robots, one blue on the left and one orange on the right, hold a large yellow envelope between them against a beige background decorated with colorful geometric shapes (circles, triangles) and bell icons in pink, purple, and green.

Sending Push Notifications from Replit with Firebase

By Seth Black • Updated: September 17, 2026

Programming · 5 min read

Like this kind of writing? Get one email a week with notes on startups, AI, and the occasional strong opinion about Python: subscribe to the newsletter.

I've been shipping small tools on Replit for years now. It's perfect for those side projects that need to go from idea to production in an afternoon. And lately I've been building more tools that run background jobs. Those jobs finish whenever they feel like it, which means users either refresh a page like it's 2004... or you notify them.

When one of those projects needs to ping users, Firebase Cloud Messaging (FCM) is my go-to. No servers to maintain, no message queues to debug, it's an API that just works. It's not "magic," but it's close enough that I'll allow it.

This post shows how to send an FCM push notification from a Replit backend using either TypeScript (Node) or Python.

Firebase docs for context: Firebase Cloud Messaging


What you need (in human terms)

You'll need:

A Firebase project with Cloud Messaging enabled. This is where Firebase tracks your app, your credentials, and your ability to send messages without being escorted out of the building.

A service account key JSON file. This is the important one for server-side sending. Firebase uses it to verify you're actually you, and not some random person trying to spam notifications. (Yes, the JSON is annoyingly large.)

A device token from the client you're targeting (mobile app, web app, etc). This is basically the "address" you're sending the notification to. Also: tokens expire, rotate, and generally behave like they have free will.

A Replit project running Node/TypeScript and/or Python.

One thing that got me the first time: make sure you're using the service account key, not the web API key. They look like they belong together, but they do completely different jobs. The web API key won't work for server-side sending, and you'll waste time thinking you made a coding mistake. (You didn't. Firebase just enjoys ambiguity.)


Store secrets safely in Replit (so you don't create your own incident report)

In Replit, open Secrets and add:

Don't commit this JSON file to your repo. I've seen too many startups learn this lesson the expensive way when their Firebase bill explodes because someone scraped their public GitHub repo. "We got hacked" is rarely a fun meeting, especially when it was actually "we published our keys."


TypeScript example (Node)

Install the Admin SDK:

npm i firebase-admin

Send a notification:

import admin from "firebase-admin";

// Parse the JSON we stored in Replit secrets.
// If you pasted it wrong (missing a quote/brace), Node will let you know immediately.
// It will also ruin your whole day.
const serviceAccount = JSON.parse(process.env.FIREBASE_SERVICE_ACCOUNT_JSON!);

// Firebase needs this check or it'll initialize multiple times and throw errors,
// especially in serverless-ish or hot-reload-ish environments.
if (!admin.apps.length) {
  admin.initializeApp({
    credential: admin.credential.cert(serviceAccount),
  });
}

export async function sendPush(token: string) {
  const message = {
    token,

    // notification = what most platforms show by default (title/body)
    notification: {
      title: "Hello from Replit",
      body: "This came from Firebase Cloud Messaging.",
    },

    // data = extra key/value pairs your app can read
    // Useful when you want the client to navigate somewhere, refresh state, etc.
    data: {
      source: "replit",
      kind: "job_finished",
    },
  };

  const res = await admin.messaging().send(message);
  console.log("Sent:", res);
}

sendPush(process.env.FCM_DEVICE_TOKEN!);

Notes that save hours (because I've donated those hours already)

I've burned a bunch of hours on permission errors. Double-check that your service account actually has the right access. In many setups, you want something like Firebase Cloud Messaging Admin permissions. If you're seeing PERMISSION_DENIED, it's almost never "a bug in your code." It's IAM.

Also yes, tokens expire. I spent an multiple afternoons debugging code that was perfectly fine. The token had expired overnight. I changed nothing. Reality changed around me. This happens with Google oAuth tokens too. You're welcome.


Python example

The Python version is nearly identical, which is actually kind of nice. Firebase keeps their SDKs consistent across languages. (If only every vendor did that, half of us could retire early.)

Install:

pip install firebase-admin

Send a notification:

import os
import json
import firebase_admin
from firebase_admin import credentials, messaging

# Parse the JSON we stored in Replit secrets.
# If you accidentally pasted the wrong thing, json.loads will fail loudly (as it should).
service_account = json.loads(os.environ["FIREBASE_SERVICE_ACCOUNT_JSON"])

# Firebase needs this check or it'll initialize multiple times and throw errors.
if not firebase_admin._apps:
    cred = credentials.Certificate(service_account)
    firebase_admin.initialize_app(cred)

def send_push(token: str):
    msg = messaging.Message(
        token=token,
        notification=messaging.Notification(
            title="Hello from Replit",
            body="Python says hi via FCM."
        ),
        # Data payload is great for app routing, background refresh, etc.
        data={"source": "replit", "kind": "job_finished"},
    )

    resp = messaging.send(msg)
    print("Sent:", resp)

send_push(os.environ["FCM_DEVICE_TOKEN"])

Python-specific gotcha I've hit: make sure you're not mixing up environment variables between local and Replit runs. Locally you might have a .env file or shell exports, and on Replit you're using Secrets. When it "works on my machine" here, it usually means "my machine had the env var and Replit didn't."


Troubleshooting (the non-theoretical kind)

If you're getting Invalid token errors, your client token is either wrong or expired. Tokens don't last forever, and they can change when the app reinstalls, clears data, or refreshes registration.

If notifications aren't showing up, check if your app is in the foreground. Most mobile OSes won't show a notification banner for the active app. Your message may still arrive, but you'll need client-side handlers to display it or update UI.

If it works locally but not in Replit, you probably fat-fingered the JSON when pasting it into secrets. Missing a closing brace will ruin your whole day. Also double-check you pasted the service account JSON and not some other Firebase credential. Firebase has several keys, and only one of them is the one you want here.


Wrap-up

Push notifications used to require standing up your own infrastructure or paying for expensive third-party services. Firebase turned it into a straightforward API call with generous free tier limits. For most side projects and early-stage startups, that's exactly what you need: simple, reliable, and not another system you have to babysit.

-Sethers

Share this post
Newsletter

One email, once a week.

Notes on databases, systems, and the occasional strong opinion about Python. No spam, unsubscribe anytime.

Seth Black
Written by

Seth Black

Engineer and founder based in Texas. Writes about databases, AI, and running things in production. Embeds in small teams as lead engineer.

More from Programming

View all →
Programming

What's the Differences Between Relational, NoSQL, Vectors, and Caches

Apr 14, 2026
Programming

Git and Source Control in AI Land

Apr 14, 2026
Programming

Domains, DNS, and why it feels fragile

Apr 14, 2026