> This page is part of the [Customer.io documentation](https://docs.customer.io). For the complete index, see [llms.txt](https://docs.customer.io/llms.txt).
> Last updated: October 22, 2026
> To take actions in Customer.io from an agent, use the [Customer.io CLI](https://docs.customer.io/ai/cli/get-started.md) or the [MCP server](https://docs.customer.io/ai/mcp/get-started.md).

# Send web push

Add a Web Push action to an automation or broadcast, write your notification, and choose which of a person's browsers receive it.

Web push is turned on per account

Web push is available to accounts that Customer.io has turned it on for. If you don’t see a **Web** tab under [**Settings > Push**](https://fly.customer.io/env/last/settings/actions/push/web), contact your account manager.

Before you send, [set up web push](https://docs.customer.io/messaging/channels/push/web/set-up/) on your website, so that the people you want to reach have web devices on their profiles.

## Create a web push

1.  In your automation or broadcast workflow, drag a **Web Push** action into your workflow.
2.  Click the action to change its settings, like **Sending behavior**, **Subscription preference**, and [action conditions](https://docs.customer.io/messaging/send/workflows/action-conditions/). The **Platform** setting shows **Web**, because this action only sends to browsers.
3.  Click **Add content** to open the composer. Use the **Rich Push** editor. Customer.io doesn’t send **Custom Payload** messages to browsers.
4.  Fill in the [notification fields](#fields).
5.  Choose which browsers receive the message in **Device**. See [last used device or all devices](#device-audience).
6.  Click **Send test…** to [test your message](#test), then save it.

The preview shows a generic notification. Each browser draws notifications differently, so see [what each browser shows](https://docs.customer.io/messaging/channels/push/web/overview/#what-each-browser-shows) before you rely on images or buttons.

## Notification fields

Field

Required

Description

**Title**

Recommended

The first line of the notification. Every browser shows it. Keep it short; most platforms show one or two lines. If you leave it blank, the notification’s title is “Customer.io”.

**Message**

Yes

The body of the notification. Every browser shows it, but most platforms cut off long text.

**Image**

No

A large image. Only Chrome and Edge on Windows and Chrome on Android show it.

**Link**

No

The `https://` page that opens when someone clicks the notification. If you leave it blank, the notification opens your site’s home page.

**Notification icon**

No

A small `https://` image, like your logo, that appears beside your text. Safari doesn’t show it.

**Badge**

No

A small, single-color `https://` image. Only Chrome on Android shows it, in the status bar. Use a shape on a transparent background; Android shows it as a silhouette.

**Action buttons**

No

Up to two buttons, each with a label of up to 32 characters and an optional `https://` URL. A button without a URL opens your **Link**. Safari doesn’t show buttons.

**Custom Data**

No

Extra key-value pairs with string values. Customer.io sends them with the notification as `custom_data`. The Customer.io service worker doesn’t display them; if you import our worker into your own, your code can read them.

**Notification Sound** doesn’t affect web push. **App** does: browsers aren’t tied to one of your apps, so keep your default app selected. Selecting another app excludes browsers. There is no website selector.

Browsers limit how much content a notification can carry. Below the action buttons, the composer shows how many bytes your web fields use, out of a budget of 3,179 bytes. The count is before Liquid renders, so leave room for personalized values. If a rendered notification is too large, Customer.io doesn’t send it to that browser.

### Advanced options

Open **Advanced options** to change how browsers present your notification. Browser and operating system support varies: unsupported options can be ignored. Test on the browsers your audience uses; these settings don’t override notification permissions or Focus settings.

Control

How to use it

**Keep on screen until dismissed**

Requests that the notification stay visible until someone dismisses it. Browsers can ignore this request.

**Silent (no sound or vibration)**

Requests a silent notification. Clear **Vibration pattern (Android)** before turning this on.

**Replace earlier notification with the same tag**

Give related notifications the same tag to replace an earlier notification. Use 1–64 characters: `A–Z`, `a–z`, `0–9`, `_`, `.`, `:`, or `-`. Liquid is allowed.

**Alert again when replacing**

Requests another alert when replacing a notification. Set a tag first. Sound and vibration still depend on **Silent** and the browser’s settings.

**Vibration pattern (Android)**

Enter milliseconds alternating vibration and pauses, like `200, 100, 200` (up to 10 values). Turn off **Silent** first. Other platforms can ignore vibration.

**Language**

Set a language tag, like `en` or `pt-BR`. This describes the notification’s language; it doesn’t translate your text.

**Text direction**

Choose **Auto**, **Left to right**, or **Right to left**. Browsers can ignore direction.

**Show time (event time instead of arrival)**

Set the displayed time using Liquid that renders to epoch milliseconds or an ISO-8601 date. This doesn’t schedule delivery.

**App badge count (Safari / Home Screen apps)**

Set a whole number from `0` to `99999`, or Liquid that renders to one. `0` clears the badge. Requires browser support for app badges; this is separate from the **Badge** image above.

For an event-triggered order automation using the latest Liquid version, set the tag to `order-{{ event.order_id | default: "general" }}`. The fallback groups updates without an order ID under `order-general`; choose a fallback that fits your replacement behavior. Turn on **Alert again when replacing** if each update should request an alert. A rendered tag with unsupported characters, like spaces or `#`, is dropped along with **Alert again when replacing**; the notification still sends without replacement.

In **Show time (event time instead of arrival)**, use the time of the event that triggered your automation:

```
{{ event_timestamp | times: 1000 }}
```

Customer.io event timestamps use seconds; multiplying by `1000` converts them to milliseconds. If you turn on **Silent**, leave the vibration pattern empty. Check receipt and replacement behavior on a real browser before sending to your audience.

### Personalize with Liquid

You can use [Liquid](https://docs.customer.io/messaging/liquid/using-liquid/) in every field, including the URLs. When Customer.io sends a notification, it renders each field and checks that the icon, badge, and button URLs start with `https://`. If one of them doesn’t, Customer.io removes that field and sends the rest.

A Liquid error stops the notification

If a field uses a variable that doesn’t exist for the person or the event, like `{{event.request_id}}`, Customer.io doesn’t send the notification, while your website and data integration report no errors. What happens depends on the field:

*   **Notification icon**, **Badge**, **Action buttons**, **Replace earlier notification with the same tag**, **Show time (event time instead of arrival)**, and **App badge count (Safari / Home Screen apps)** only go to browsers. If one of them doesn’t render, Customer.io doesn’t send the notification to browsers and retries the delivery with `WEBPUSH_RENDER_FAILED`, in case the data arrives in the meantime. If it still doesn’t render, the delivery fails.
*   **Title**, **Message**, **Image**, **Link**, and **Custom Data** are the same fields that app push uses (app push calls **Link** *Deep Link or Web URL*). If one of them doesn’t render, Customer.io doesn’t send the notification, and the delivery fails with a Liquid error, like any other push message.

Make sure your event or person always has every variable your message uses, or give optional variables a fallback. On the latest Liquid version, use the `default` filter. With legacy Liquid, use [if/else fallbacks](https://docs.customer.io/messaging/liquid/using-liquid/#fallback-for-legacy-liquid). For example, on the latest version:

```
https://example.com/bookings/{{ event.request_id | default: "" }}
```

## Last used device or all devices

The **Device** setting decides which eligible browsers on a profile receive the message, subject to the selected **App**.

*   **Last used device** (default) sends to one browser: the person’s most recently used web device. Customer.io only picks among the person’s browsers, so their iOS and Android devices don’t count. Registering or updating a browser’s device updates its `last_used` time. The plugin re-registers when the subscription, signed-in person, or device metadata changes, including a new browser major version. Upgrading to SDK 0.7.0 also re-registers existing subscribers once on their next identified visit to record the site origin. An unchanged site visit doesn’t otherwise need to re-register or update `last_used`.
*   **All profile devices** sends to every eligible browser on the profile, subject to the selected **App**. Use it when people should get the notification in every browser they’ve turned on alerts in, like a laptop and a work computer.

For more about these options, see [choose which devices receive your message](https://docs.customer.io/messaging/channels/push/send-push/#device-audience).

Only web push actions send to browsers

A **Push Notification** action with **Platform** set to **iOS & Android** sends to a person’s iOS and Android devices, not their browsers. To reach browsers, add a web push action. **Last used device** picks within the action’s platform, so a person’s newest browser never takes the place of their phone for a mobile push, and their phone never takes the place of their browser for a web push.

## Test your message

Click **Send test…** in the composer and paste your browser’s subscription JSON. To get it, sign in to your site in the browser you want to test, open your browser’s JavaScript console, and run:

```
JSON.stringify(await cioanalytics.webPush.subscription())
```

Use the test to check that the notification appears and clicking it opens your link. Settings and composer test sends check the push relay without creating a real automation delivery. They don’t create an automation **Sent** tab entry or automation delivery metrics. To verify those, trigger a real test automation as described in [test your setup](https://docs.customer.io/messaging/channels/push/web/set-up/#test).

The test uses your **Sample data** to render Liquid. If a field doesn’t render, the dialog tells you which variable is missing. Fix it before you send to your audience, because a field that doesn’t render stops the notification for real recipients too.

## Transactional push doesn’t reach browsers

[Transactional push messages](https://docs.customer.io/messaging/send/transactional/push/) go to a person’s iOS and Android devices only. To send push to browsers, use a web push action in an automation or broadcast.

## Metrics

Web push uses the same [push metrics](https://docs.customer.io/messaging/channels/push/notifications-tracking/) as app push:

*   **Sent**: Customer.io handed the notification to the browser’s push service.
*   **Delivered**: The Customer.io service worker showed the notification. A browser that couldn’t receive the notification right away reports delivered when it shows the notification, if the push service still holds it.
*   **Opened**: Someone clicked the notification or one of its buttons.
