A New Policy for Mozilla Location Service

Several years ago we started a geolocation experiment called the Mozilla Location Service (MLS) to create a location service built on open-source software and powered through crowdsourced location data. MLS provides geolocation lookups based on publicly observable cell tower and WiFi access point information. MLS has served the public interest by providing location information to open-source operating systems, research projects, and developers.

Today Mozilla is announcing a policy change regarding MLS. Our new policy will impose limits on commercial use of MLS. Mozilla has not made this change by choice. Skyhook Holdings, Inc. contacted Mozilla some time ago and alleged that MLS infringed a number of its patents. We subsequently reached an agreement with Skyhook that avoids litigation. While the terms of the agreement are confidential, we can tell you that the agreement exists and that our MLS policy change relates to it. We can also confirm that this agreement does not change the privacy properties of our service: Skyhook does not receive location data from Mozilla or our users.

Our new policy preserves the public interest heart of the MLS project. Mozilla has never offered any commercial plans for MLS and had no intention to do so. Only a handful of entities have made use of MLS API Query keys for commercial ventures. Nevertheless, we regret having to impose new limits on MLS. Sometimes companies have to make difficult choices that balance the massive cost and uncertainty of patent litigation against other priorities.

Mozilla has long argued that patents can work to inhibit, rather than promote, innovation. We continue to believe that software development, and especially open-source software, is ill-served by the patent system. Mozilla endeavors to be a good citizen with respect to patents. We offer a free license to our own patents under the Mozilla Open Software Patent License Agreement. We will also continue our advocacy for a better patent system.

Under our new policy, all users of MLS API Query keys must apply.  Non-commercial users (such as academic, public interest, research, or open-source projects) can request an MLS API Query key capped at a daily usage limit of 100,000. This limit may be increased on request. Commercial users can request an MLS API Query key capped at a daily usage limit of 100,000. The daily limit cannot be increased for commercial uses and those keys will expire after 3 months. In effect, commercial use of MLS will now be of limited duration and restricted in volume.

Existing keys will expire on March 1, 2020. We encourage non-commercial users to re-apply for continued use of the service. Keys for a small number of commercial users that have been exceeding request limits will expire sooner. We will reach out to those users directly.

Location data and services are incredibly valuable in today’s connected world. We will continue to provide an open-source and privacy respecting location service in the public interest. You can help us crowdsource data by opting-in to the contribution option in our Android mobile browser.

Making Firefox Accounts more transparent in Firefox

Over the past few months the Firefox Accounts team have been working on making users more aware of Firefox Account and the benefits of having an account. This phase had an emphasis on our desktop users because we believe that would have the highest impact.

The problem

Based on user testing and feedback, most of our users did not clearly understand all of the advantages of having a Firefox Account. Most users failed to understand the value proposition of an account and why they should create one. Additionally, if they had an account, users were not always aware of the current signed in status in the browser. This meant that users could be syncing their private data and not fully aware they were doing that.

The benefits of an account we wanted to highlight were:

  • Sync bookmarks, passwords, tabs, add-ons and history among all your Firefox Browsers
  • Send tabs between Firefox Browsers
  • Stores your data encrypted
  • Use one account to log in to supported Firefox services (Ex. Monitor, Lockbox, Send)

Previously, users that downloaded Firefox would only see the outlined benefits at a couple touch points in the browser. Specifically these points below

First run page (New installation)

What’s New page (Firefox installation upgraded)

Browsing to preferences and clicking Firefox Account menu

If a user failed to create an account and login during the first two points, it was very unlikely that they would organically discover Firefox Accounts at point three. Having only these touch points meant that users could not always set-up a Firefox Account at their own pace.

Our solution

Our team decided that we needed to make this easier and solicited input from our Growth and Marketing teams, particularly on how to best showcase and highlight our features. From these discussions, we decided to experiment with putting a top level account menu item next to the Firefox application menu. Our hypothesis was that having a top level menu would drive engagement and reinforce the benefits of Firefox Accounts.

We believed that having an account menu item at this location would give users more visibility into their account status and allow them to quickly manage it.

While most browsers have some form of a top level account menu, we decided to experiment with the feature because Firefox users are more privacy focused and might not behave as other browser users.

New Firefox Account Toolbar menu

Our designs

The initial designs for this experiment had a toolbar menu left of the Firefox application menu. This menu could not be removed and was always fixed. After consulting with engineering teams, having a fixed menu could more easily be achieved as a native browser feature. However, because of the development cycle of Firefox browser (new releases every 6 weeks), we would have to wait 6 weeks to test our experiment as a native feature.

Initial toolbar design

If the requirement that the menu was not fixed was lifted then we could ship a Shield web extension experiment and get results much more quickly (2-3 weeks). Shield experiments are not tied to a specific Firefox release schedule and users can opt in and out of them. This means that Firefox can install shield experiments, run them and then uninstall them at the end of the experiment.

After discussions with product and engineering teams, we decided to develop and ship the Shield web extension experiment (with these known limitations) and while that experiment was gathering data, start development of the native browser version for the originally spec design. There was a consensus between product and engineering teams that if the experiment was successful, it should eventually live as a native feature in the browser and not as a web extension.

Account toolbar Web Extension Version

Experiment results

Our experiment ran for 28 days and at the end of it, users were given a survey to fill out. There was a treatment (installed web extension) and control (did not install web extension) group. Below is a summary of the results

  • Treatment users authenticated into Firefox Account roughly 8% more
  • From survey
    • 45% of users liked the avatar
    • 45% of users were indifferent
    • 10% didn’t like it

The overall feedback for the top level menu was positive and most users were not bothered by it. Based on this we decided to update and iterate on the design for the final native browser version.

Iterating designs

While the overall feedback was positive for the new menu item, there were a few tweaks to the final design. Most notably

  • New menu is fully customizable (can be placed anywhere in Firefox browser vs fixed position)
    • After discussing with team, while having this menu fixed was in the original designs, we decided to have it not fixed because that was what the experiment ran as and it was more inline with the Firefox brand of being personalizable.
  • Ability to easily connect another device via SMS
  • View current sync tabs
  • Added ability to send current and multiple tabs to a device
  • Added ability to force a sync of data

Because we started working on the native feature while the experiment was running, we did not have to dedicate as many development resources to complete new requirements.

Final toolbar design

Check out the development bug here for more details.

Next steps

We are currently researching ways to best surface new account security features and services for Firefox Accounts using the new toolbar menu. Additionally, there are a handful of usability tweaks that we would like to add in future versions. Stay tuned!

The Firefox Account toolbar menu will be available starting in desktop Firefox 67. If you want to experiment early with it, you can download Firefox Beta 67 or Firefox Nightly.

Big thank you for all the teams and folks that helped to make this feature happen!

Upcoming WebPush Shield Study

WebPush does more than let you know you’ve got an upcoming calendar appointment or bug you about subscribing to a site’s newsletter (particularly one you just visited and have zero interest in doing). Turns out that WebPush is a pretty good way for us to do a number of things as well. Things like let you send tabs from one install of Firefox to another, or push out important certificate updates. We’ll talk about those more when we get ready to roll them out, but for now, we need to know if some of the key bits work.

One of the things we need to test is if our WebPush servers are up to the job of handling traffic, or if there might be any weird issue we might not have thought of. We’ve run tests, we’ve simulated loads, but honestly, nothing compares to real life for this sort of thing.

In the coming weeks, we’re going to be running an experiment. We’ll be using the Shield service to have your browser set up a web push connection. No data will go over that connection aside from the minimal communication that we need. It shouldn’t impact how you use Firefox, or annoy you with pop-ups. Chances are, you won’t even notice we’re doing this.

Why are we telling you if it something you wouldn’t notice? We like to be open and clear about things. You might see a reference to “dom.push.alwaysConnect” in about:config and wonder what it might mean. Shield lets us flip that switch and gives us control over how many folks at any given time hit our servers. That’s important when you want to test your server and things don’t go as planned.

In this case “dom.push.alwaysConnect” will ask your browser to open a connection to our servers. This is so we can test if our servers can handle the load. Why do it this way instead of a load test? Turns out that trying to effectively load test this is problematic. It’s hard to duplicate “real world” load and all the issues that come with it. This test will help us make sure that things don’t fall over when we make this a full feature. When that configuration flag is set to “true” your browser will try to connect to our push servers.

You can always opt out of the study, if you want, but we hope that you don’t mind being part of this. The more folks we have, and the more diverse the group, the more certain we can be that our servers are up for the challenge of keeping you safer and more in control.

Account recovery keys in Firefox Accounts

The Firefox Accounts team is in the process of releasing a new feature called Account Recovery. Previously, when a user resets their password, they would be given new encryption keys and could potentially risk losing any synced bookmarks, passwords and browsing history. With Account Recovery, a user can keep their encryption keys and not lose any data.

A more technical overview of how this feature works can be found here.

If you are interested in trying it out, simply goto your Firefox Account settings and click Account Recovery. If you do not see the Account Recovery option, you might not be in the rollout group yet. However, it can be manually enabled using these instructions.

From panel, click Generate, confirm your password and save the key displayed.

In the event you forget your password, you will be prompted for this key to recover your data.

Thanks and kudos to our security team, designers, developers, testers and everyone else that helped to make this feature happen!

Two-step authentication in Firefox Accounts

Two-step authentication in Firefox Accounts


Starting on 5/23/2018, we are beginning a phased rollout to allow Firefox Accounts users to opt into two-step authentication. If you enable this feature, then in addition to your password, an additional security code will be required to log in.

We chose to implement this feature using the well-known authentication standard TOTP (Time-based One-Time Password). TOTP codes can be generated using a variety of authenticator applications. For example, Google Authenticator, Duo and Authy all support generating TOTP codes.

Additionally, we added support for single-use recovery codes in the event you lose access to the TOTP application. It is recommend that you save your recovery codes in a safe spot since they can be used to bypass TOTP.

To enable two-step authentication, go to your Firefox Accounts preferences and click “Enable” on the “Two-step authentication” panel.

Note: If you do not see the Two-step authentication panel, you can manually enable it by following these instructions.

Using one of the authenticator applications, scan the QR code and then enter the security code it displays. Doing this will confirm your device, enable TOTP and show your recovery codes.

Note: After setup, make sure you download and save your recovery codes in a safe location! You will not be able to see them again, unless you generate new ones.

Once two-step authentication is enabled, every login will require a security code from your TOTP device.

Thanks to everyone that helped to work on this feature including UX designers, engineers, quality assurance and security teams!

Changing your primary email in Firefox Accounts

The Firefox Accounts team recently introduced the ability for a user to change their primary email address. Being one of the main developers to work this feature, I wanted to share my experience and give a summary on what it took to get this feature to our users.

Our motivation

Based on user feedback, the most common scenario for changing your primary email was losing access to that email account. This email was often associated with work or an organization they no longer were apart of.

Most account systems would simply allow the user to continue logging in with their old email. However, because your Firefox Account can contain sensitive information, we needed to have an extra layer of security. This came in the form of us running heuristics on the login attempt and prompting you to verify that email address. For example, logging in from a device that has not had a login in over 3 days would require an email confirmation.

If you can no longer access that email address, you are locked out of your account and the data it contains. This caters on the side of security versus user experience. The most common workaround was to create a new account and sync your existing data. This method meant that you could lose data on the old account if you were syncing from a new device.

Design decisions

Once we decided to move forward with the feature, we created a high level plan on how it was going to be done. Exploratory work was already done a few years ago that outlined the risks and a possible solution. We used this as a basis for our initial design.

One of the complexities of changing your Firefox Account email is that our login procedure combines email and password to derive a strong encryption key. This original design decision was driven by a security requirement and meant that we could not perform an email change in one operation, because we would lose part of the key.

Considering these factors, we opted to create an intermediate feature, adding a secondary email address, that would solve a few of the original problems while being designed to allow easy changes to the user’s primary email. Secondary email addresses also receive security notifications and can verify login requests.

While implementing secondary emails, we migrated from a single email on the account database table, to supporting multiple emails in separate emails table. Each email has a couple of flags to signify whether or not they are they primary and verified. Additionally, we wrote several migration scripts that populated our new emails table while falling back to using the account table if there wasn’t any email. Doing this phased approached allowed us to safely rollback if any issues were found.

After adding the secondary email feature, we were able to simplify our database which allowed the actual email change to be flipping the isPrimary flag on an email. After that, our quality assurance team made sure there were no regressions and everything worked as expected.

Updating browsers and our services

Once the secondary emails feature landed, we then set our focus on updating all of our clients and services to support changing the primary email. In addition to the server side changes, updates were needed for each browser and service that uses a Firefox Account.

Before any of the browsers would pickup the email change, they needed to be updated to properly detect and fetch the updated profile information. The Desktop, Android, iOS, Pocket, AMO and Basket  teams each had unique problems while trying to add support for this feature. If interested, you can check out the complete bug tree. Each one of the updates could be worthy of their own blog post.

After adding and verifying a secondary email, you now have the option to make it your primary!

Turning it on

While the Firefox Account team’s development schedule is fairly fixed, we could not risk turning this feature on until all of the clients and services were updated. This meant that we had to wait on external teams to finish testing and integrating the changes. Each browser and team could have a different schedule and timeline for getting fixes in.

While the complete feature rollout took several months, we were able to test the majority of the change email feature by putting it behind a feature flag and having users opt into it. Several bugs were found this way as it gave our QA a way to access feature in production.

The final bug to remove the feature flag was merged in February which turned it on for everyone.

Final thoughts

Our team kept putting this feature off because of the complexity and all the components involved. While the final verdict on how well this retains users is not out, I am happy that we were able to push through these and give a long requested feature to our user base. Below is a usage graph that shows that users are already changing their address and keeping their account updated.

Thanks to everyone and teams that helped review, development and push the changes needed for this feature!

Why WebPush Doesn’t Allow Broadcast

One of the common questions we get working on the Web Push backend team, is “How do I broadcast a Push message to all my customers?” The short answer is, you don’t.

In the early days, I used say that Web Push is more like a doorbell than a walkie-talkie. Web push is designed to send a timely message of interest from a website to a specific customer. Like a doorbell, it’s pretty much a one to one thing.

There’s a lot you can do once you make the decision to make things one to one rather than one to many. For instance, it’s very easy to do end-to-end encryption. When you encrypt a message you make it so that only a certain number of people can read it. Ideally, a message should be readable by just two people, the person who created the message and the person who receives the message. Right now, a message is encrypted by you for your recipient and Mozilla can’t read it. We don’t have and will never see the key.

You can share the message with a group by sharing the key, but with every share, you run the risk of the key leaking to someone you don’t want to have it. On my wall at work, there are two pictures. One is of the TSA luggage security keys, the other is of a Yale 1620 key. The second one you may not have heard about. The 1620 is the master firefighter key for much of New York City, and many firefighters and building supervisors have a copy. Technically, it’s against the law to have an unauthorized copy, but that doesn’t stop many folks from acquiring a copy or some publications from printing very high definition versions so you can make them at home with a blank and a metal file. It’s a good example of having encryption that’s not really encryption. We want to avoid that kind of situation.

There are other issues at hand with doing a “broadcast”. One of the bigger ones is that “broadcast” has already been solved, every time you go to a web page. Web pages can be delivered securely via any number of means, and there are a whole host of existing protocols and procedures in place that make delivery fast and safe. How a browser knows to check a given page is a bit fuzzy, but again, there are hosts of protocols and functions in place to make that as lightweight as possible.

An important consideration for broadcasts (and one to one messages too): when do they need to arrive? Now? Soon? What does that mean really mean in the context of your app? Our system tries hard to deliver messages quickly, but we will never deliver them instantly. Likewise, there are all sorts of reasons that a device may not get a message quickly. The device may be off, out of range, or traveling and have no net access for the next few hours. Once a device is back online, it will try to reconnect and retrieve messages, but even this is essentially polling, and again, there are long established methods for doing these sorts of things. Determining how soon is “now” may help determine when your app really needs to poll for the broadcast elements.

Much like a doorbell or Philips head screwdriver, Web push is a tool for a specific task. It’s possible to use it for other tasks, but it’s ill suited and there are far better tools available.

If you’re interested in some of the more technical details, you can read much of the lively discussion that was held among the working group, as well as a preliminary draft for a webpush-aggregation service.

Managing Push Subscriptions

This article is a continuing series about using and working with WebPush and Mozilla’s WebPush service. This article is not meant to be a general guide, but instead offer suggestions and insight into best using the service. Some knowledge of Javascript, Python, or other technologies is presumed.

Sending out push notifications to Customers is a great way to ensure that they’re up-to-date with information that is of importance to them. What’s also important is that your growing business use this service efficiently so you don’t wind up wasting money creating and sending out messages that no one will ever read. In addition, not honoring response codes could lead to your server being blocked or severely limited in how many Subscription Updates it may be able to send.

Mozilla’s WebPush server will let you know if a Customer has unsubscribed, but it’s important that you notice and act on these. I have created a simple demonstration program that can help you understand what you will need to consider when creating a Push Subscription service.


First off, let’s refresh a few terms we’ll use in this article:

App – The javascript web application that receives the decoded Push Message.

Customer – The end recipient of the Push Message.

Push Message – The content of the Subscription Update to be provided to the App by the User Agent and potentially viewed by the Customer.

Push Server – The service located at the endpoint URL which handles delivery of the Subscription Update to the User Agent.

Subscription – A Customer request to be updated. A Subscription contains an endpoint URL, and a set of keys that are to be used to encode the Push Message.

Subscription Provider – The subscription provider sends out Push Messages to User Agents to be read by Customers.

Subscription Update – The message sent by the Subscription Provider to the Push Server.

User Agent – The Customer’s browser, specifically, code internal to the browser which processes and routes Push Messages to your App.

Sending Subscription Updates

Sending a Push Message is a fairly simple operation, and thanks to modern libraries, easily done. It’s important to pay attention to the returned result. When a Subscription Provider sends a Subscription Update, the Push Service returns a response code. In most cases, the response code will be either 201 (Message Created) or 202 (Message Accepted). There’s subtle differences between those, but those differences are not important right now.

What is important is to know that the Push Server will return an HTTP error code along with a body that has extra information about what may have happened.

A possible 404 return message body might look like:
'errno': 102,
'message': 'Request did not validate invalid token',
'code': 404,
'more_info': 'http://autopush.readthedocs.io/en/latest/http.html#error-codes',
'error': 'Not Found'

In this case, there was a problem with the URL. More than likely it was corrupted at some point. In any case, the URL is now invalid and should not be tried again. The Customer’s record can be safely removed from storage.

This is also true for 410 return codes. These are subscribers who no longer wish to receive your updates. A Customer may unsubscribe for any number of reasons, and you should respect that choice. You can always ask the Customer to resubscribe later.

The Demo App

As an example, I’ve created a very simple demonstration project that uses Python3. This project does require Python3 to take advantage of native async programming to speed up delivery and message handling. Follow the steps in the README file to get it started. You can then navigate to http://localhost:8200 to see the page.

The test page (located in the /page directory) is very simple and only starts the process of subscribing once the Customer has requested it. Clicking the one button on the page will automatically create a Subscription Request and offer some script snippets you can use to send messages to the App.

To see what happens when a user unsubscribes, disable the permissions using the page information pip:
An example of using the page information pip to unregister from WebPush messages

If you try to send a Subscription Update to that customer again, you will receive an error and should drop the subscription. An example error from pusher may look like:

Failed to send to HMV192av: No such subscription
For more info, see: http://autopush.readthedocs.io/en/latest/http.html#error-codes
Dropping no longer valid user: HMV192av

In this case, the subscription for user HMV192av has been removed, and the record was dropped from the user database.

It’s important to only ask your Customers to subscribe once they understand what they’re subscribing to. A Customer who is asked to subscribe to WebPush notifications without being given a clear indication of what they’re being offered may click the “Always Block Notifications” option. An example of a user blocking WebPush message subscription

When a user blocks notifications from your site, you may never get a chance to ask them again.

Following these simple guidelines will ensure that both you and your Customers are happy using the WebPush service.

Device management coming to your Firefox Account

Today we are beginning a phased roll out of a new account management feature to Firefox Accounts users. This new feature aims to give users a clear overview of all services attached to the account, and provide our users with full control over their synced devices.

With the new “Devices” panel in your Firefox Accounts settings, you will be able to manage all your devices that use Firefox Sync. The devices section shows all connected Firefox clients on Desktop, iOS and Android, making it an excellent addition to those who use Firefox Sync on multiple devices. Use the “Disconnect” button to get rid of the devices that you don’t want to sync.

This feature will be made available to all users soon and we have a lot more planned to make account management easier for everyone. Here’s what the first version of the devices view looks like:

To stay organized you can easily rename your device in the Sync Preferences using the “Device Name” panel:

Updating Device Name

Thanks to everyone who worked on this feature: Phil Booth, Jon Buckley, Vijay Budhram, Alex Davis, Ryan Feeley, Vlad Filippov, Mark Hammond, Ryan Kelly , Sean McArthur, John Morrison, Edouard Oger, Shane Tomlinson. Special thanks to developers on the mobile teams that helped with device registration: Nick Alexander, Michael Comella, Stephan Leroux and Brian Nicholson.

If you want to get involved with the Firefox Accounts open source project please visit: fxa.readthedocs.io. Make sure to visit the Firefox Accounts settings page in the coming weeks to take more control over your devices!

Sending VAPID identified WebPush Notifications via Mozilla’s Push Service


The Web Push API provides the ability to deliver real time events (including data) from application servers (app servers) to their client-side counterparts (applications), without any interaction from the user. In other parts of our Push documentation we provide a general reference for the application API and a basic client usage tutorial. This document addresses the app server side portion in detail, including integrating VAPID and Push message encryption into your server effectively, and how to avoid common issues.

Update: There have been several updates since this document was originally published. Where appropriate, I’ve corrected things to reflect state as of April 25th, 2017.
Note: Much of this document presumes you’re familiar with programming as well as have done some light work in cryptography. Unfortunately, since this is new technology, there aren’t many libraries available that make sending messages painless and easy. As new libraries come out, we’ll add pointers to them, but for now, we’re going to spend time talking about how to do the encryption so that folks who need it, or want to build those libraries can understand enough to be productive.

Bear in mind that Push is not meant to replace richer messaging technologies like Google Cloud Messaging (GCM), Apple Push Notification system (APNs), or Microsoft’s Windows Notification System (WNS). Each has their benefits and costs, and it’s up to you as developers or architects to determine which system solves your particular set of problems. Push is simply a low cost, easy means to send data to your application.

Push Summary

The Push system looks like:
A diagram of the push process flow

Application The user facing part of the program that interacts with the browser in order to request a Push Subscription, and receive Subscription Updates.
Application Server The back-end service that generates Subscription Updates for delivery across the Push Server.
Push The system responsible for delivery of events from the Application Server to the Application.
Push Server The server that handles the events and delivers them to the correct Subscriber. Each browser vendor has their own Push Server to handle subscription management. For instance, Mozilla uses autopush.
Subscription A user request for timely information about a given topic or interest, which involves the creation of an Endpoint to deliver Subscription Updates to. Sometimes also referred to as a “channel”.
Endpoint A specific URL that can be used to send a Push Message to a specific Subscriber.
Subscriber The Application that subscribes to Push in order to receive updates, or the user who instructs the Application to subscribe to Push, e.g. by clicking a “Subscribe” button.
Subscription Update An event sent to Push that results in a Push Message being received from the Push Server.
Push Message A message sent from the Application Server to the Application, via a Push Server. This message can contain a data payload.

The main parts that are important to Push from a server-side perspective are as follows we’ll cover all of these points below in detail:

Identifying Yourself

Mozilla goes to great lengths to respect privacy, but sometimes, identifying your feed can be useful.

Mozilla offers the ability for you to identify your feed content, which is done using the Voluntary Application server Identification for web Push VAPID specification. This is a set of header values you pass with every subscription update. One value is a VAPID key that validates your VAPID claim, and the other is the VAPID claim itself a set of metadata describing and defining the current subscription and where it has originated from.

VAPID is only useful between your servers and our push servers. If we notice something unusual about your feed, VAPID gives us a way to contact you so that things can go back to running smoothly. In the future, VAPID may also offer additional benefits like reports about your feeds, automated debugging help, or other features.

In short, VAPID is a bit of JSON that contains an email address to contact you, an optional URL that’s meaningful about the subscription, and a timestamp. I’ll talk about the timestamp later, but really, think of VAPID as the stuff you’d want us to have to help you figure out something went wrong.

It may be that you only send one feed, and just need a way for us to tell you if there’s a problem. It may be that you have several feeds you’re handling for customers of your own, and it’d be useful to know if maybe there’s a problem with one of them.

Generating your VAPID key

The easiest way to do this is to use an existing library for your language. VAPID is a new specification, so not all languages may have existing libraries.
Currently, we’ve collected several libraries under https://github.com/web-push-libs/vapid and are very happy to learn about more.

Fortunately, the method to generate a key is fairly easy, so you could implement your own library without too much trouble

The first requirement is an Elliptic Curve Diffie Hellman (ECDH) library capable of working with Prime 256v1 (also known as “p256” or similar) keys. For many systems, the OpenSSL package provides this feature. OpenSSL is available for many systems. You should check that your version supports ECDH and Prime 256v1. If not, you may need to download, compile and link the library yourself.

At this point you should generate a EC key for your VAPID identification. Please remember that you should NEVER reuse the VAPID key for the data encryption key you’ll need later. To generate a ECDH key using openssl, enter the following command in your Terminal:

openssl ecparam -name prime256v1 -genkey -noout -out vapid_private.pem

This will create an EC private key and write it into vapid_private.pem. It is important to safeguard this private key. While you can always generate a replacement key that will work, Push (or any other service that uses VAPID) will recognize the different key as a completely different user.

You’ll need to send the Public key as one of the headers . This can be extracted from the private key with the following terminal command:

openssl ec -in vapid_private.pem -pubout -out vapid_public.pem

Creating your VAPID claim

VAPID uses JWT to contain a set of information (or “claims”) that describe the sender of the data. JWTs (or Javascript Web Tokens) are a pair of JSON objects, turned into base64 strings, and signed with the private ECDH key you just made. A JWT element contains three parts separated by “.”, and may look like:


  1. The first element is a “header” describing the JWT object. This JWT header is always the same the static string {typ:"JWT",alg:"ES256"} which is URL safe base64 encoded to eyJ0eXAiOiJKV1QiLCJhbGciOiJFUzI1NiJ9. For VAPID, this string should always be the same value.
  2. The second element is a JSON dictionary containing a set of claims. For our example, we’ll use the following claims:
     "sub": "mailto:admin@example.com",
     "aud": "https://push.services.mozilla.com",
     "exp": "1463001340"

    The required claims are as follows:

    1. sub : The “Subscriber” a mailto link for the administrative contact for this feed. It’s best if this email is not a personal email address, but rather a group email so that if a person leaves an organization, is unavailable for an extended period, or otherwise can’t respond, someone else on the list can. Mozilla will only use this if we notice a problem with your feed and need to contact you.
    2. aud : The “Audience” is a JWT construct that indicates the recipient scheme and host (e.g. for an endpoint like https://updates.push.services.mozilla.com/wpush/v1/gAAAAABY..., the “aud” would be https://updates.push.services.mozilla.com. Some push services will require this field. Please consult your documentation.
    3. exp : “Expires” this is an integer that is the date and time that this VAPID header should remain valid until. It doesn’t reflect how long your VAPID signature key should be valid, just this specific update. Normally this value is fairly short, usually the current UTC time + no more than 24 hours. A long lived “VAPID” header does introduce a potential “replay” attack risk, since the VAPID headers could be reused for a different subscription update with potentially different content.


    Feel free to add additional items to your claims. This info really should be the sort of things you want to get at 3AM when your server starts acting funny. For instance, you may run many AWS S3 instances, and one might be acting up. It might be a good idea to include the AMI-ID of that instance (e.g. “aws_id”:”i-5caba953″). You might be acting as a proxy for some other customer, so adding a customer ID could be handy. Just remember that you should respect privacy and should use an ID like “abcd-12345” rather than “Mr. Johnson’s Embarrassing Bodily Function Assistance Service”. Just remember to keep the data fairly short so that there aren’t problems with intermediate services rejecting it because the headers are too big.

Once you’ve composed your claims, you need to convert them to a JSON formatted string, ideally with no padding space between elements1, for example:


Then convert this string to a URL-safe base64-encoded string, with the padding ‘=’ removed. For example, if we were to use python:

   import base64
   import json
   # These are the claims
   claims = {"sub":"mailto:admin@example.com",
             "aud": "https://push.services.mozilla.com",
   # convert the claims to JSON, then encode to base64
   body = base64.urlsafe_b64encode(json.dumps(claims))
   print body

would give us


This is the “body” of the JWT base string.

The header and the body are separated with a ‘.’ making the JWT base string.


  • The final element is the signature. This is an ECDH signature of the JWT base string created using your VAPID private key. This signature is URL safe base64 encoded, “=” padding removed, and again joined to the base string with an a ‘.’ delimiter.

    Generating the signature depends on your language and library, but is done by the ecdsa algorithm using your private key. If you’re interested in how it’s done in Python or Javascript, you can look at the code in https://github.com/web-push-libs/vapid.

    Since your private key will not match the one we’ve generated, the signature you see in the last part of the following example will be different.


  • Forming your headers

    The VAPID claim you assembled in the previous section needs to be sent along with your Subscription Update as an Authorization header vapid token. This is a compound key consisting of the JWT token you just created (designated as t=) and the VAPID public key as its value formatted as a URL safe, base64 encoded DER formatted string of the raw key.
    If you like, you can cheat here and use the content of “vapid_public.pem”. You’ll need to remove the “-----BEGIN PUBLIC KEY------” and “-----END PUBLIC KEY-----” lines, remove the newline characters, and convert all “+” to “-” and “/” to “_”.
    The complete token should look like so:

    Authorization: vapid t=eyJ0eXAiOiJKV1QiLCJhbGciOiJFUzI1NiJ9.eyJhdWQiOiJodHRwczovL3B1c2guc2VydmljZXMubW96aWxsYS5jb20iLCJzdWIiOiJtYWlsdG86YWRtaW5AZXhhbXBsZS5jb20iLCJleHAiOiIxNDYzMDAxMzQwIn0.y_dvPoTLBo60WwtocJmaTWaNet81_jTTJuyYt2CkxykLqop69pirSWLLRy80no9oTL8SDLXgTaYF1OrTIEkDow,k=BAS7pgV_RFQx5yAwSePfrmjvNm1sDXyMpyDSCL1IXRU32cdtopiAmSysWTCrL_aZg2GE1B_D9v7weQVXC3zDmnQ

    Note: the header should not contain line breaks. Those have been added here to aid in readability

    You can validate your work against the VAPID test page this will tell you if your headers are properly encoded. In addition, the VAPID repo contains libraries for JavaScript and Python to handle this process for you.

    We’re happy to consider PRs to add libraries covering additional languages.

    Receiving Subscription Information

    Your Application will receive an endpoint and key retrieval functions that contain all the info you’ll need to successfully send a Push message. See Using the Push API for details about this. Your application should send this information, along with whatever additional information is required, securely to the Application Server as a JSON object.

    Such a post back to the Application Server might look like this:

        "customerid": "123456",
        "subscription": {
            "endpoint": "https://updates.push.services.mozilla.com/push/v1/gAAAA…",
            "keys": {
                "p256dh": "BOrnIslXrUow2VAzKCUAE4sIbK00daEZCswOcf8m3TF8V…",
                "auth": "k8JV6sjdbhAi1n3_LDBLvA"
        "favoritedrink": "warm milk"

    In this example, the “subscription” field contains the elements returned from a fulfilled PushSubscription. The other elements represent additional data you may wish to exchange.

    How you decide to exchange this information is completely up to your organization. You are strongly advised to protect this information. If an unauthorized party gained this information, they could send messages pretending to be you. This can be made more difficult by using a “Restricted Subscription”, where your application passes along your VAPID public key as part of the subscription request. A restricted subscription can only be used if the subscription carries your VAPID information signed with the corresponding VAPID private key. (See the previous section for how to generate VAPID signatures.)

    Subscription information is subject to change and should be considered “opaque”. You should consider the data to be a “whole” value and associate it with your user. For instance, attempting to retain only a portion of the endpoint URL may lead to future problems if the endpoint URL structure changes. Key data is also subject to change. The app may receive an update that changes the endpoint URL or key data. This update will need to be reflected back to your server, and your server should use the new subscription information as soon as possible.

    Sending a Subscription Update Without Data

    Subscription Updates come in two varieties: data free and data bearing. We’ll look at these separately, as they have differing requirements.

    Data Free Subscription Updates

    Data free updates require no additional App Server processing, however your Application will have to do additional work in order to act on them. Your application will simply get a “push” message containing no further information, and it may have to connect back to your server to find out what the update is. It is useful to think of Data Free updates like a doorbell “Something wants your attention.”

    To send a Data Free Subscription, you POST to the subscription endpoint. In the following example, we’ll include the VAPID header information. Values have been truncated for presentation readability.

    curl -v -X POST\
      -H "Authorization: Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJFUzI1NiJ9.eyJhdWQiOiJodHR…"\
      -H "Crypto-Key: p256ecdsa=BA5vkyMXVfaKuehJuecNh30-NiC7mT9gM97Op5d8LiAKzfIezLzC…"\
      -H "TTL: 0"\

    This should result in an Application getting a “push” message, with no data associated with it.

    To see how to store the Push Subscription data and send a Push Message using a simple server, see our Using the Push API article.

    Data Bearing Subscription Updates

    Data Bearing updates are a lot more interesting, but do require significantly more work. This is because we treat our own servers as potentially hostile and require “end-to-end” encryption of the data. The message you send across the Mozilla Push Server cannot be read. To ensure privacy, however, your application will not receive data that cannot be decoded by the User Agent.

    There are libraries available for several languages at https://github.com/web-push-libs, and we’re happy to accept or link to more.

    The encryption method used by Push is Elliptic Curve Diffie Hellman (ECDH) encryption, which uses a key derived from two pairs of EC keys. If you’re not familiar with encryption, or the brain twisting math that can be involved in this sort of thing, it may be best to wait for an encryption library to become available. Encryption is often complicated to understand, but it can be interesting to see how things work.

    If you’re familiar with Python, you may want to just read the code for the http_ece package. If you’d rather read the original specification, that is available. While the code is not commented, it’s reasonably simple to follow.

    Data encryption summary

    Update: This section describes the ECE Draft-03 “aesgcm” encryption encoding.
    This encoding is currently the most widely supported, however it is out-of-date.
    The core principles remain the same across versions, however the final formatting differs.
    There is some effort being made to have the UA report back the forms of encryption content it supports,
    however that may not be available at time of publication.
    You’re strongly encouraged to use a ECE library when possible.
    • Octet An 8 bit byte of data (between \x00 and \xFF)
    • Subscription data The subscription data to encode and deliver to the Application.
    • Endpoint the Push service endpoint URL, received as part of the Subscription data.
    • Receiver key The p256dh key received as part of the Subscription data.
    • Auth key The auth key received as part of the Subscription data.
    • Payload The data to encrypt, which can be any streamable content between 2 and 4096 octets.
    • Salt 16 octet array of random octets, unique per subscription update.
    • Sender key A new ECDH key pair, unique per subscription update.

    Web Push limits the size of the data you can send to between 2 and 4096 octets. You can send larger data as multiple segments, however that can be very complicated. It’s better to keep segments smaller. Data, whatever the original content may be, is also turned into octets for processing.

    Each subscription update requires two unique items a salt and a sender key. The salt is a 16 octet array of random octets. The sender key is a ECDH key pair generated for this subscription update. It’s important that neither the salt nor sender key be reused for future encrypted data payloads. This does mean that each Push message needs to be uniquely encrypted.

    The receiver key is the public key from the client’s ECDH pair. It is base64, URL safe encoded and will need to be converted back into an octet array before it can be used. The auth key is a shared “nonce”, or bit of random data like the salt.

    Emoji Based Diagram

    Subscription Data Per Update Info Update
    🎯 Endpoint 🔑 Private Server Key 📄Payload
    🔒 Receiver key (‘p256dh’) 🗝 Public Server Key
    💩 Auth key (‘auth’) 📎 Salt
    🔐 Private Sender Key
    ✒️ Public Server Key
    🏭 Build using / derive
    🔓 message encryption key
    🎲 message nonce

    Encryption uses a fabricated key and nonce. We’ll discuss how the actual encryption is done later, but for now, let’s just create these items.

    Creating the Encryption Key and Nonce

    The encryption uses HKDF (Hashed Key Derivation Function) using a SHA256 hash very heavily.

    Creating the secret

    The first HKDF function you’ll need will generate the common secret (🙊), which is a 32 octet value derived from a salt of the auth (💩) and run over the string “Content-Encoding: auth\x00”.
    So, in emoji =>
    🔐 = 🔑(🔒);
    🙊 = HKDF(💩, “Content-Encoding: auth\x00”).🏭(🔐)

    An example function in Python could look like so:

    # receiver_key must have "=" padding added back before it can be decoded.
    # How that's done is an exercise for the reader.
    # 🔒
    receiver_key = subscription['keys']['p256dh']
    # 🔑
    server_key = pyelliptic.ECC(curve="prime256v1")
    # 🔐
    sender_key = server_key.get_ecdh_key(base64.urlsafe_b64decode(receiver_key))
    secret = HKDF(
        length = 32,
        salt = auth,
        info = "Content-Encoding: auth\0").derive(sender_key)

    The encryption key and encryption nonce

    The next items you’ll need to create are the encryption key and encryption nonce.

    An important component of these is the context, which is:

    • A string comprised of ‘P-256’
    • Followed by a NULL (“\x00”)
    • Followed by a network ordered, two octet integer of the length of the decoded receiver key
    • Followed by the decoded receiver key
    • Followed by a networked ordered, two octet integer of the length of the public half of the sender key
    • Followed by the public half of the sender key.

    As an example, if we have an example, decoded, completely invalid public receiver key of ‘RECEIVER’ and a sample sender key public key example value of ‘sender’, then the context would look like:

    # ⚓ (because it's the base and because there are only so many emoji)
    root = "P-256\x00\x00\x08RECEIVER\x00\x06sender"

    The “\x00\x08” is the length of the bogus “RECEIVER” key, likewise the “\x00\x06” is the length of the stand-in “sender” key. For real, 32 octet keys, these values will most likely be “\x00\x20” (32), but it’s always a good idea to measure the actual key rather than use a static value.

    The context string is used as the base for two more HKDF derived values, one for the encryption key, and one for the encryption nonce. In python, these functions could look like so:

    In emoji:
    🔓 = HKDF(💩 , “Content-Encoding: aesgcm\x00” + ⚓).🏭(🙊)
    🎲 = HKDF(💩 , “Content-Encoding: nonce\x00” + ⚓).🏭(🙊)

    encryption_key = HKDF(
        info="Content-Encoding: aesgcm\x00" + context).derive(secret)
    # 🎲
    encryption_nonce = HDKF(
        info="Content-Encoding: nonce\x00" + context).derive(secret)

    Note that the encryption_key is 16 octets and the encryption_nonce is 12 octets. Also note the null (\x00) character between the “Content-Encoding” string and the context.

    At this point, you can start working your way through encrypting the data 📄, using your secret 🙊, encryption_key 🔓, and encryption_nonce 🎲.

    Encrypting the Data

    The function that does the encryption (encryptor) uses the encryption_key 🔓 to initialize the Advanced Encryption Standard (AES) function, and derives the Galois/Counter Mode (G/CM) Initialization Vector (IV) off of the encryption_nonce 🎲, plus the data chunk counter. (If you didn’t follow that, don’t worry. There’s a code snippet below that shows how to do it in Python.) For simplicity, we’ll presume your data is less than 4096 octets (4K bytes) and can fit within one chunk.
    The IV takes the encryption_nonce and XOR’s the chunk counter against the final 8 octets.

    def generate_iv(nonce, counter):
        (mask,) = struct.unpack("!Q", nonce[4:])  # get the final 8 octets of the nonce
        iv = nonce[:4] + struct.pack("!Q", counter ^ mask)  # the first 4 octets of nonce,
                                                         # plus the XOR'd counter
        return iv

    The encryptor prefixes a “\x00\x00” to the data chunk, processes it completely, and then concatenates its encryption tag to the end of the completed chunk. The encryptor tag is a static string specific to the encryptor. See your language’s documentation for AES encryption for further information.

    def encrypt_chunk(chunk, counter, encryption_nonce, encryption_key):
        encryptor = Cipher(algorithms.AES(encryption_key),
        return encryptor.update(b"\x00\x00" + chunk) +
               encryptor.finalize() +
    def encrypt(payload, encryption_nonce, encryption_key):
        result = b""
        counter = 0
        for i in list(range(0, len(payload)+2, 4096):
            result += encrypt_chunk(

    Sending the Data

    Encrypted payloads need several headers in order to be accepted.

    The Crypto-Key header is a composite field, meaning that different things can store data here. There are some rules about how things should be stored, but we can simplify and just separate each item with a semicolon “;”. In our case, we’re going to store three things, a “keyid”, “p256ecdsa” and “dh”.

    “keyid” is the string “p256dh”. Normally, “keyid” is used to link keys in the Crypto-Key header with the Encryption header. It’s not strictly required, but some push servers may expect it and reject subscription updates that do not include it. The value of “keyid” isn’t important, but it must match between the headers. Again, there are complex rules about these that we’re safely ignoring, so if you want or need to do something complex, you may have to dig into the Encrypted Content Encoding specification a bit.

    “p256ecdsa” is the public key used to sign the VAPID header (See Forming your Headers). If you don’t want to include the optional VAPID header, you can skip this.

    The “dh” value is the public half of the sender key we used to encrypt the data. It’s the same value contained in the context string, so we’ll use the same fake, stand-in value of “sender”, which has been encoded as a base64, URL safe value. For our example, the base64 encoded version of the string ‘sender’ is ‘c2VuZGVy’

    Crypto-Key: p256ecdsa=BA5v…;dh=c2VuZGVy;keyid=p256dh

    The Encryption Header contains the salt value we used for encryption, which is a random 16 byte array converted into a base64, URL safe value.

    Encryption: keyid=p256dh;salt=cm5kIDE2IGJ5dGUgc2FsdA

    The TTL Header is the number of seconds the notification should stay in storage if the remote user agent isn’t actively connected. “0” (Zed/Zero) means that the notification is discarded immediately if the remote user agent is not connected; this is the default. This header must be specified, even if the value is “0”.

    TTL: 0

    Finally, the Content-Encoding Header specifies that this content is encoded to the aesgcm standard.

    Content-Encoding: aesgcm

    The encrypted data is set as the Body of the POST request to the endpoint contained in the subscription info. If you have requested that this be a restricted subscription and passed your VAPID public key as part of the request, you must include your VAPID information in the POST.

    As an example, in python:

    headers = {
        'crypto-key': 'p256ecdsa=BA5v…;dh=c2VuZGVy;keyid=p256dh',
        'content-encoding': 'aesgcm',
        'encryption': 'keyid=p256dh;salt=cm5kIDE2IGJ5dGUgc2FsdA',
        'ttl': 0,

    A successful POST will return a response of 201, however, if the User Agent cannot decrypt the message, your application will not get a “push” message. This is because the Push Server cannot decrypt the message so it has no idea if it is properly encoded. You can see if this is the case by:

    • Going to about:config in Firefox
    • Setting the dom.push.loglevel pref to debug
    • Opening the Browser Console (located under Tools > Web Developer > Browser Console menu.

    When your message fails to decrypt, you’ll see a message similar to the following
    The debugging console displaying "The service worker for scope 'https://mozilla-services.github.io/WebPushDataTestPage/' encountered an error decryption the a push message:, with a message and where to look for more info

    You can use values displayed in the Web Push Data Encryption Page to audit the values you’re generating to see if they’re similar. You can also send messages to that test page and see if you get a proper notification pop-up, since all the key values are displayed for your use.

    You can find out what errors and error responses we return, and their meanings by consulting our server documentation.

    Subscription Updates

    Nothing (other than entropy) lasts forever. There may come a point where, for various reasons, you will need to update your user’s subscription endpoint. There are any number of reasons for this, but your code should be prepared to handle them.

    Your application’s service worker will get a onpushsubscriptionchange event. At this point, the previous endpoint for your user is now invalid and a new endpoint will need to be requested. Basically, you will need to re-invoke the method for requesting a subscription endpoint. The user should not be alerted of this, and a new endpoint will be returned to your app.

    Again, how your app identifies the customer, joins the new endpoint to the customer ID, and securely transmits this change request to your server is left as an exercise for the reader. It’s worth noting that the Push server may return an error of 410 with an errno of 103 when the push subscription expires or is otherwise made invalid. (If a push subscription has expired several months ago, the server may return a different errno value.


    Push Data Encryption can be very challenging, but worthwhile. Harder encryption means that it is more difficult for someone to impersonate you, or for your data to be read by unintended parties. Eventually, we hope that much of this pain will be buried in libraries that allow you to simply call a function, and as this specification is more widely adopted, it’s fair to expect multiple libraries to become available for every language.

    See also:

    1. WebPush Libraries: A set of libraries to help encrypt and send push messages.
    2. VAPID lib for python or javascript can help you understand how to encode VAPID header data.


    1. Technically, you don’t need to strip the whitespace from JWS tokens. In some cases, JWS libraries take care of that for you. If you’re not using a JWS library, it’s still a very good idea to make headers and header lines as compact as possible. Not only does it save bandwidth, but some systems will reject overly lengthy header lines. For instance, the library that autopush uses limits header line length to 16,384 bytes. Minus things like the header, signature, base64 conversion and JSON overhead, you’ve got about 10K to work with. While that seems like a lot, it’s easy to run out if you do things like add lots of extra fields to your VAPID claim set.