How to subscribe to topics with web browser using Firebase Cloud Messaging

There is no direct API to subscribe clients to topics in the Firebase Cloud Messaging SDK for JavaScript. Instead you can subscribe a token to a topic through the REST API. Calling this API requires that you specify the FCM server key, which means that you should only do this on a trusted environment, such as your development machine, a server you control, or Cloud Functions. This is necessary, since having the FCM server key allows sending messages on your app's behalf to all of your app's users.

It turns out that in my tests I was using an older project, where client API keys were allows to subscribe to topics. This ability has been removed from newer projects for security reasons.

In Node.js you could for example call the REST API to create a relation mapping for an app instance like this:

function subscribeTokenToTopic(token, topic) {
  fetch('https://iid.googleapis.com/iid/v1/'+token+'/rel/topics/'+topic, {
    method: 'POST',
    headers: new Headers({
      'Authorization': 'key='+fcm_server_key
    })
  }).then(response => {
    if (response.status < 200 || response.status >= 400) {
      throw 'Error subscribing to topic: '+response.status + ' - ' + response.text();
    }
    console.log('Subscribed to "'+topic+'"');
  }).catch(error => {
    console.error(error);
  })
}

Where fcm_server_key is the FCM server key, taken from the Firebase console of your project.


Update: the option to subscribe a FCM token a topic is now also part of most of the Firebase Admin SDK, which makes it easier to do this on supported environments. For more on this, see the documentation on subscribing the client app to a topic/


Any one looking for php solution find below since you are going to use Api key of server so don't do this on client side

client side fire base js code

// Initialize Firebase
var config = {
    apiKey: "xxxx",
    authDomain: "yyy",
    databaseURL: "zzzz",
    projectId: "aaaa",
    storageBucket: "bbbbb",
    messagingSenderId: "ccc"
  };
firebase.initializeApp(config);

const messaging = firebase.messaging();

messaging.requestPermission()
.then(function() {
  console.log('Notification permission granted.');
  return messaging.getToken();
})
.then(function(token) {

//send this token to server
  console.log(token); // Display user token
})
.catch(function(err) { // Happen if user deney permission

  console.log('Unable to get permission to notify.', err);
});

messaging.onMessage(function(payload){
    console.log('onMessage',payload);
})

server side code by php curl

$headers = array
    ('Authorization: key=' . API_ACCESS_KEY,
    'Content-Type: application/json');

$ch = curl_init();
// browser token you can get it via ajax from client side
$token = 'drVdtCt82qY:APA91bEZb99GvoS9knv-cp5ThVBiYGBjUwl_Ewj2tKaRFwp7HoG347utaNKbgLWmkxpGadABtIg-DspPUh5sC_bc2JrBKVw10Ejg72nYxZgD2wBU-adYJo0yi03lX22s5K2UEp6gwnMv';
curl_setopt($ch, CURLOPT_URL, "https://iid.googleapis.com/iid/v1/$token/rel/topics/testIshakTopic");
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);
curl_setopt($ch, CURLOPT_POSTFIELDS, array());
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
$result = curl_exec($ch);
echo "The Result : " . $result;

Hope it will help for PHP developers


    import firebase from 'firebase/app';
    import 'firebase/messaging';

    const config = {
        apiKey: "xxxx",
        authDomain: "xxx",
        databaseURL: "xxx",
        projectId: "xxx",
        storageBucket: "xxxx",
        messagingSenderId: 'xxxxx',
        appId: 'xxxxx',
        measurementId: 'xxxxxx'
      };
      firebase.initializeApp(config);
        try {
              if (firebase.messaging.isSupported()) {
                const messaging = firebase.messaging();
                messaging
                  .getToken({
                    vapidKey: VAPID_KEY
                  })
                  .then((currentToken) => {
                    if (currentToken) {
                                           subscribeTokenToTopic(currentToken,FirebaseAdminTopic);
                    }
                  })
                  .catch((err) => {
                    console.log('Error to get token', err);
                  });

                messaging.onMessage((payload) => {
                  console.log(payload.notification)
                });

                // Otherwise, we need to ask the user for permission
                if (Notification.permission !== 'granted') {
                  Notification.requestPermission();
                }
              } else {
                console.log('firebase messaging not supported');
              }
            } catch (err) {
              console.log(err);
            }
            
            
            
        function subscribeTokenToTopic(token, topic) {
          fetch(`https://iid.googleapis.com/iid/v1/${token}/rel/topics/${topic}`, {
            method: 'POST',
            headers: new Headers({
              Authorization: `key=${FCM_SERVER_KEY}`
            })
          })
            .then((response) => {
              if (response.status < 200 || response.status >= 400) {
                console.log(response.status, response);
              }
              console.log(`"${topic}" is subscribed`);
            })
            .catch((error) => {
              console.error(error.result);
            });
          return true;
        }

 

Franks solution is still valid. However you need to have a server that does the subscribe for you.

function subscribeTokenToTopic(token, topic) {
  fetch('https://myserver.com/'+token+'/rel/topics/'+topic, {
    method: 'POST',
    headers: new Headers({
      'Authorization': 'Bearer '+ oauthToken
    })
  }).then(response => {
    if (response.status < 200 || response.status >= 400) {
      throw 'Error subscribing to topic: '+response.status + ' - ' + response.text();
    }
    console.log('Subscribed to "'+topic+'"');
  }).catch(error => {
    console.error(error);
  })
}

then your server has a rest call something like this:

(java spring)

@RequestMapping(value = "/{token}/rel/topics/{topic}", method = RequestMethod.POST)
public ResponseEntity<?> subscribeTokenToTopic(@PathVariable("token") String token, @PathVariable("topic") String topic)  throws ServletException {
  URL url = new URL("https://iid.googleapis.com/iid/v1/"+token+"/rel/topics/"+topic);
  // make https post call here.. 
  ...
}

Hopefully that makes sense.