This guide explains the process of configuring and using FCM with Novu, from getting your credentials to sending your first notification.
Step 1: Generate your service account key from Firebase
Get your project’s service account credentials from the Firebase Console.
Log in to the Firebase console .
Create a new Firebase project or select an existing project.
Click the gear icon ⚙️ next to Project Overview .
Select Project settings .
Click the Service accounts tab.
Click the Generate new private key button. A confirmation menu appears.
Click Generate key to download a JSON file containing your credentials.
Open the downloaded JSON file and ensure it contains these fields:
{
"type" : "service_account" ,
"project_id" : "PROJECT_ID" ,
"private_key_id" : "PRIVATE_KEY_ID" ,
"private_key" : "PRIVATE_KEY" ,
"client_email" : "FIREBASE_ADMIN_SDK_EMAIL" ,
"client_id" : "CLIENT_ID" ,
"auth_uri" : "https://accounts.google.com/o/oauth2/auth" ,
"token_uri" : "https://oauth2.googleapis.com/token" ,
"auth_provider_x509_cert_url" : "https://www.googleapis.com/oauth2/v1/certs" ,
"client_x509_cert_url" : "CLIENT_X509_CERT_URL"
}
Step 2: Connect FCM to Novu
Add the credentials to your FCM integration in the Novu dashboard.
Log in to the Novu dashboard.
Navigate to the Integration Store .
Click Connect provider .
Click the Push tab, then select Firebase Cloud Messaging (FCM) .
Open the JSON file you downloaded from Firebase in Step 1 .
Copy the entire content of the JSON file and paste it into the Service Account field in the FCM integration modal.
Click Create Integration to save the integration.
Step 3: Register a subscriber’s device token
Before Novu can send a push notification to your subscriber, you must associate their device’s unique push token with their Novu subscriber profile.
You can do this by making an API call to update the subscriber’s credentials .
Node.js
Python
Go
PHP
.NET
Java
cURL
import { Novu } from '@novu/api' ;
import { ChatOrPushProviderEnum } from "@novu/api/models/components" ;
const novu = new Novu ({ secretKey: "<NOVU_SECRET_KEY>" });
await novu . subscribers . credentials . update (
{
providerId: ChatOrPushProviderEnum . Fcm ,
integrationIdentifier: "string" ,
credentials: { deviceTokens: [ "token1" , "token2" ] },
},
"subscriberId"
);
import os
import novu_py
from novu_py import Novu
with Novu( secret_key = os.getenv( "NOVU_SECRET_KEY" , "" )) as novu:
novu.subscribers.credentials.update(
subscriber_id = "subscriberId" ,
update_subscriber_channel_request_dto = {
"provider_id" : novu_py.ChatOrPushProviderEnum. FCM ,
"credentials" : { "deviceTokens" : [ "token1" , "token2" ]},
"integration_identifier" : "string" ,
},
)
import (
" context "
" os "
novugo " github.com/novuhq/novu-go "
" github.com/novuhq/novu-go/models/components "
)
s := novugo . New ( novugo . WithSecurity ( os . Getenv ( "NOVU_SECRET_KEY" )))
res , err := s . Subscribers . Credentials . Update ( context . Background (), "subscriberId" , components . UpdateSubscriberChannelRequestDto {
ProviderID : components . ChatOrPushProviderEnumFcm ,
IntegrationIdentifier : novugo . String ( "string" ),
Credentials : components . ChannelCredentials {
DeviceTokens : [] string { "token1" , "token2" },
},
}, nil )
use novu ;
use novu\Models\ Components ;
$sdk = novu\ Novu :: builder () -> setSecurity ( '<NOVU_SECRET_KEY>' ) -> build ();
$sdk -> subscribersCredentials -> update (
subscriberId : 'subscriberId' ,
updateSubscriberChannelRequestDto : new Components\ UpdateSubscriberChannelRequestDto (
providerId : Components\ ChatOrPushProviderEnum :: Fcm ,
integrationIdentifier : 'string' ,
credentials : new Components\ ChannelCredentials (
deviceTokens : [ 'token1' , 'token2' ],
),
),
);
using Novu ;
using Novu . Models . Components ;
using System . Collections . Generic ;
var sdk = new NovuSDK ( secretKey : "<NOVU_SECRET_KEY>" );
await sdk . Subscribers . Credentials . UpdateAsync (
subscriberId : "subscriberId" ,
updateSubscriberChannelRequestDto : new UpdateSubscriberChannelRequestDto () {
ProviderId = ChatOrPushProviderEnum . Fcm ,
IntegrationIdentifier = "string" ,
Credentials = new ChannelCredentials () {
DeviceTokens = new List < string > { "token1" , "token2" },
},
});
import co.novu.Novu;
import co.novu.models.components. * ;
import java.util.List;
Novu novu = Novu . builder (). secretKey ( "<NOVU_SECRET_KEY>" ). build ();
novu . subscribers (). credentials (). update ()
. subscriberId ( "subscriberId" )
. body ( UpdateSubscriberChannelRequestDto . builder ()
. providerId ( ChatOrPushProviderEnum . FCM )
. integrationIdentifier ( "string" )
. credentials ( ChannelCredentials . builder ()
. deviceTokens ( List . of ( "token1" , "token2" ))
. build ())
. build ())
. call ();
curl -L -X PUT 'https://api.novu.co/v1/subscribers/<SUBSCRIBER_ID>/credentials' \
-H 'Content-Type: application/json' \
-H 'Authorization: ApiKey <NOVU_SECRET_KEY>' \
-d '{
"providerId": "fcm",
"credentials": {
"deviceTokens": [
"token1",
"token2"
]
},
"integrationIdentifier": "string"
}'
Novu automatically removes invalid device tokens from a subscribers’ profile and then sends the failure details to the MESSAGE_FAILED webhook.
Step 4: Send a notification
Now you’re ready to send a push notification. You can trigger a notification to a subscriber who has a registered device token.
Node.js
Python
Go
PHP
.NET
Java
cURL
import { Novu } from '@novu/api' ;
const novu = new Novu ({ secretKey: "<NOVU_SECRET_KEY>" });
await novu . trigger ({
workflowId: "workflowId" ,
to: { subscriberId: "subscriberId" , },
payload: {
"key" : "value"
},
overrides: {
"providers" : {
"fcm" : {
"topic" : "topic-123"
}
}
},
});
import os
import novu_py
from novu_py import Novu
with Novu( secret_key = os.getenv( "NOVU_SECRET_KEY" , "" )) as novu:
novu.trigger( trigger_event_request_dto = novu_py.TriggerEventRequestDto(
workflow_id = "workflowId" ,
to = { "subscriber_id" : "subscriberId" },
payload = {
"key" : "value"
},
overrides = {
"providers" : {
"fcm" : {
"topic" : "topic-123"
}
}
},
))
import (
" context "
" os "
novugo " github.com/novuhq/novu-go "
" github.com/novuhq/novu-go/models/components "
)
s := novugo . New ( novugo . WithSecurity ( os . Getenv ( "NOVU_SECRET_KEY" )))
res , err := s . Trigger ( context . Background (), components . TriggerEventRequestDto {
WorkflowID : "workflowId" ,
To : components . CreateToSubscriberPayloadDto ( components . SubscriberPayloadDto {
SubscriberID : "subscriberId" ,
}),
Payload : map [ string ] any {
"key" : "value" ,
},
Overrides : map [ string ] any {
"providers" : map [ string ] any {
"fcm" : map [ string ] any {
"topic" : "topic-123" ,
},
},
},
}, nil )
use novu ;
use novu\Models\ Components ;
$sdk = novu\ Novu :: builder () -> setSecurity ( '<NOVU_SECRET_KEY>' ) -> build ();
$sdk -> trigger (
triggerEventRequestDto : new Components\ TriggerEventRequestDto (
workflowId : 'workflowId' ,
to : new Components\ SubscriberPayloadDto ( subscriberId : 'subscriberId' ),
payload : [ 'key' => 'value' ],
overrides : [
'providers' => [
'fcm' => [
'topic' => 'topic-123' ,
],
],
],
),
);
using Novu ;
using Novu . Models . Components ;
using System . Collections . Generic ;
var sdk = new NovuSDK ( secretKey : "<NOVU_SECRET_KEY>" );
await sdk . TriggerAsync ( triggerEventRequestDto : new TriggerEventRequestDto () {
WorkflowId = "workflowId" ,
To = To . CreateSubscriberPayloadDto ( new SubscriberPayloadDto () { SubscriberId = "subscriberId" }),
Payload = new Dictionary < string , object >() {
{ "key" , "value" },
},
Overrides = new Overrides () {
Providers = new Dictionary < string , Dictionary < string , object >>() {
{ "fcm" , new Dictionary < string , object >() {
{ "topic" , "topic-123" },
} },
},
},
});
import co.novu.Novu;
import co.novu.models.components. * ;
import java.util.Map;
Novu novu = Novu . builder (). secretKey ( "<NOVU_SECRET_KEY>" ). build ();
novu . trigger ()
. body ( TriggerEventRequestDto . builder ()
. workflowId ( "workflowId" )
. to ( To2 . of ( SubscriberPayloadDto . builder (). subscriberId ( "subscriberId" ). build ()))
. payload ( Map . of ( "key" , "value" ))
. overrides ( TriggerEventRequestDtoOverrides . builder ()
. additionalProperties ( Map . of ( "providers" , Map . of ( "fcm" , Map . of ( "topic" , "topic-123" ))))
. build ())
. build ())
. call ();
curl --location 'https://api.novu.co/v1/events/trigger' \
--header 'Content-Type: application/json' \
--header 'Authorization: ApiKey <NOVU_SECRET_KEY>' \
-d '{
"name": "workflowId",
"to": [
"subscriberId"
],
"payload": {
"key": "value"
},
"overrides": {
"providers": {
"fcm": {
"topic": "topic-123"
}
}
}
}'
Web push with relative links
Suppose you’re using the Firebase (FCM) provider to send push notifications to web browsers via Novu and want users to be returned to the website after clicking the notification.
In that case, you must use the link property with a relative URL.
Node.js
Python
Go
PHP
.NET
Java
cURL
import { Novu } from '@novu/api' ;
const novu = new Novu ({ secretKey: "<NOVU_SECRET_KEY>" });
await novu . trigger ({
workflowId: "workflowId" ,
to: { subscriberId: "subscriberId" , },
payload: {
"key" : "value"
},
overrides: {
"providers" : {
"fcm" : {
"webPush" : {
"fcmOptions" : {
"link" : "/foo"
}
}
}
}
},
});
import os
import novu_py
from novu_py import Novu
with Novu( secret_key = os.getenv( "NOVU_SECRET_KEY" , "" )) as novu:
novu.trigger( trigger_event_request_dto = novu_py.TriggerEventRequestDto(
workflow_id = "workflowId" ,
to = { "subscriber_id" : "subscriberId" },
payload = {
"key" : "value"
},
overrides = {
"providers" : {
"fcm" : {
"webPush" : {
"fcmOptions" : {
"link" : "/foo"
}
}
}
}
},
))
import (
" context "
" os "
novugo " github.com/novuhq/novu-go "
" github.com/novuhq/novu-go/models/components "
)
s := novugo . New ( novugo . WithSecurity ( os . Getenv ( "NOVU_SECRET_KEY" )))
res , err := s . Trigger ( context . Background (), components . TriggerEventRequestDto {
WorkflowID : "workflowId" ,
To : components . CreateToSubscriberPayloadDto ( components . SubscriberPayloadDto {
SubscriberID : "subscriberId" ,
}),
Payload : map [ string ] any {
"key" : "value" ,
},
Overrides : map [ string ] any {
"providers" : map [ string ] any {
"fcm" : map [ string ] any {
"webPush" : map [ string ] any {
"fcmOptions" : map [ string ] any {
"link" : "/foo" ,
},
},
},
},
},
}, nil )
use novu ;
use novu\Models\ Components ;
$sdk = novu\ Novu :: builder () -> setSecurity ( '<NOVU_SECRET_KEY>' ) -> build ();
$sdk -> trigger (
triggerEventRequestDto : new Components\ TriggerEventRequestDto (
workflowId : 'workflowId' ,
to : new Components\ SubscriberPayloadDto ( subscriberId : 'subscriberId' ),
payload : [ 'key' => 'value' ],
overrides : [
'providers' => [
'fcm' => [
'webPush' => [
'fcmOptions' => [
'link' => '/foo' ,
],
],
],
],
],
),
);
using Novu ;
using Novu . Models . Components ;
using System . Collections . Generic ;
var sdk = new NovuSDK ( secretKey : "<NOVU_SECRET_KEY>" );
await sdk . TriggerAsync ( triggerEventRequestDto : new TriggerEventRequestDto () {
WorkflowId = "workflowId" ,
To = To . CreateSubscriberPayloadDto ( new SubscriberPayloadDto () { SubscriberId = "subscriberId" }),
Payload = new Dictionary < string , object >() {
{ "key" , "value" },
},
Overrides = new Overrides () {
Providers = new Dictionary < string , Dictionary < string , object >>() {
{ "fcm" , new Dictionary < string , object >() {
{ "webPush" , new Dictionary < string , object >() {
{ "fcmOptions" , new Dictionary < string , object >() {
{ "link" , "/foo" },
} },
} },
} },
},
},
});
import co.novu.Novu;
import co.novu.models.components. * ;
import java.util.Map;
Novu novu = Novu . builder (). secretKey ( "<NOVU_SECRET_KEY>" ). build ();
novu . trigger ()
. body ( TriggerEventRequestDto . builder ()
. workflowId ( "workflowId" )
. to ( To2 . of ( SubscriberPayloadDto . builder (). subscriberId ( "subscriberId" ). build ()))
. payload ( Map . of ( "key" , "value" ))
. overrides ( TriggerEventRequestDtoOverrides . builder ()
. additionalProperties ( Map . of ( "providers" , Map . of ( "fcm" , Map . of (
"webPush" , Map . of ( "fcmOptions" , Map . of ( "link" , "/foo" ))))))
. build ())
. build ())
. call ();
curl --location 'https://api.novu.co/v1/events/trigger' \
--header 'Content-Type: application/json' \
--header 'Authorization: ApiKey <NOVU_SECRET_KEY>' \
-d '{
"name": "workflowId",
"to": [
"subscriberId"
],
"payload": {
"key": "value"
},
"overrides": {
"providers": {
"fcm": {
"webPush": {
"fcmOptions": {
"link": "/foo"
}
}
}
}
}
}'
Frequently asked questions
As per Firebase
pricing ,
Cloud Messaging product is free to use. If other Firebase products are used, then the cost are charged per the product.
The registration token is not a valid FCM registration token
You might come across an error such as: Sending message failed due to "The registration token is not a valid FCM registration token". This error happens because of an invalid or stale token. The fix for this is:
Remove the old tokens.
Generate a new token.
Save the new token into user subscribers.
FCM notifications sent successfully with no error but push notification is not received in device
Try to generate a new token after clearing device cache and retry with this fresh token.
Sending message failed due to 'Requested entity was not found'
This error occurs when your token is no longer valid. To fix this, generate a new token and use it.
Subscriber does not have a configured channel error
This error occurs if the FCM integration is active, but the subscriber is missing from the FCM credentials (deviceTokens). The credentials (deviceTokens) for the subscriber needs to be set.
How to send desktop notifications using FCM
Desktop notifications for websites can be sent using FCM webpush.