Blog · Guides

Segmenting push notifications with tags and user IDs

When to use the user ID and when to use tags, how to design tags that hold up, how to send to segments and user lists, and how to keep tags up to date.

Leer en español

Segmenting push notifications takes two tools: the user ID, which links each subscribed device to an account in your system so you can reach one person on all their devices, and tags, key-value pairs that describe a subscriber (plan, language, interests) so you can reach groups. You attach the user ID at sign-in; tags need updating every time the fact they describe changes.

In short:

  • A subscriber is one browser on one device; a user can have several or none.
  • Use the user ID for personal notifications and tags for stable groups.
  • Design a few tags with short, normalized text values and no personal data.
  • A segment requires all of its tags at once; an "or" needs a different approach.
  • If the data lives in your database and changes often, send to a list of users instead of copying it into tags.

Subscriber, user and tag: three different things

Most segmentation bugs come from mixing up these three concepts:

ConceptWhat it representsExampleUse it for
SubscriberOne browser on one device, with its endpointChrome on Ana's laptopDebugging delivery to one device
User IDAn account in your systemu_1842Personal alerts: "Your order has shipped"
TagAn attribute of a subscriberplan: pro, lang: enGroup alerts: "What's new for Pro customers"

Two consequences follow. First, someone with a laptop and a phone has two subscribers with the same user ID, and a send by user ID reaches both. Second, tags live on each subscriber, not on the person. If Ana upgrades to Pro on her laptop and you only update that browser's tags, her phone will still say free.

Linking the user ID

The user ID is the identifier from your own database. Attach it at two moments: when subscribing, if the person is already signed in, and when someone signs in on a browser that subscribed anonymously. With the castly SDK it looks like this:

// From the "Turn on notifications" click
await Castly.subscribe({ userId: currentUser?.id, tags: { lang: 'en' } });

// On sign-in, if this browser was already subscribed
await Castly.identify(user.id, { lang: 'en', plan: user.plan });

// On sign-out from a shared computer
await Castly.identify(null);

That last step is easy to forget. If two people share a computer and you do not unlink the ID on sign-out, the second person will receive the first person's personal notifications.

Use an opaque internal ID, never an email address or phone number. The ID travels through exports, logs and webhooks, and the less personal data it contains, the less you have to protect.

Designing tags that work

A good tag describes something that changes rarely and that many people share. In castly, each subscriber can hold up to 30 text key-value pairs; keys use letters, digits, hyphens and underscores (up to 64 characters) and values are up to 200 characters long. These examples show the difference:

Good tagBad tagWhy
plan: promrr: 149.90An amount changes every month and is useless for equality filters
deals: yes and news: yesinterests: deals,newsThe whole value is compared: deals,news does not match deals
country: usemail: ana@example.comPersonal data; for individuals you already have the user ID
signup_month: 2026-11last_seen: 2026-11-13T10:22Changes on every visit and forces endless tag rewrites

Normalize values (lowercase, no spaces, one fixed vocabulary) and remember they are text: "yes" or "1", not true. A tag written as Pro in one place and pro in another quietly splits your segment in two.

Sending to a segment or to specific users

For a group, send to a segment. A subscriber is included only if it has every listed tag at once; this example reaches anyone with plan: pro and lang: en:

{
  "to": { "type": "segment", "tags": { "plan": "pro", "lang": "en" } },
  "title": "Your November report is ready",
  "url": "https://example.com/reports/2026-11"
}

For specific people, use the users target with your IDs (up to 1,000 per request). It reaches every active device of each person:

{
  "to": { "type": "users", "userIds": ["u_1842", "u_2093"] },
  "title": "Your order has left the warehouse"
}

Because a segment combines tags with "and", an "or" takes a little thought:

  • Different values of the same key (plan: pro or plan: scale): send once per value. A subscriber has only one value per key, so nobody gets the notification twice.
  • Different keys (deals or news): two sends would double up for anyone who has both. Build the list of users in your database and send to users, or create a derived tag that represents the union.

Before a large send, check the audience size: GET /v1/subscribers?status=active&tag=plan:pro lists active subscribers with that tag. Every target and filter is in the API reference.

Keeping tags up to date

Tags go stale. There are three moments to update them:

  • On sign-in: identify() with the account's current tags, as in the example above.
  • When the person changes a preference on your site: setTags(). It replaces the browser's tags with the ones you pass, so always send the full set, not just the one that changed.
  • When the data changes on your server, for example a plan change arriving through your billing provider's webhook: update all of the user's devices from the backend.
const API = 'https://castly.sh/v1';
const headers = {
  Authorization: `Bearer ${process.env.PUSH_SECRET_KEY}`,
  'Content-Type': 'application/json',
};

async function updatePlanTag(userId, plan) {
  const url = `${API}/subscribers?userId=${encodeURIComponent(userId)}&status=active&limit=100`;
  const { data } = await fetch(url, { headers }).then((r) => r.json());
  for (const sub of data) {
    await fetch(`${API}/subscribers/${sub.id}`, {
      method: 'PATCH',
      headers,
      body: JSON.stringify({ tags: { ...sub.tags, plan } }), // keep the other tags
    });
  }
}

If a value changes very often or only exists in your database, do not copy it into tags: query it at send time and send to the resulting list of users. Tags work best for attributes that are stable or known to the browser itself, such as language or the topics the person picked on your site.

Frequently asked questions

Can I use an email address as the user ID?

Technically it is just text, but it is a bad idea. An email address is personal data that would end up in logs and exports, and it also changes; an opaque internal ID is safer and more stable.

What about subscribers who never signed in?

They have no user ID, so you can only reach them through tags or a send to everyone. When they sign in, identify() links their subscription to the account without asking for permission again.

Are tags shared across one user's devices?

No. Each subscriber has its own tags, so the same person's phone and laptop can hold different values. If the attribute belongs to the account, update it on all of their devices from the server.

How many tags should I use?

As few as you can: a handful of well-defined, documented keys with a closed vocabulary. The technical limit is usually generous; the real risk is ending up with tags nobody maintains and segments that no longer mean what their names say.

How do I know whether one segment responds better than another?

Compare clicks over delivered notifications for each segment, using the same kind of message and the same time window. We explain how in measuring push click-through rate.

Put it into practice with castlyWeb and PWA push notifications for developers: an SDK under 3 KB, a REST API and delivery logs for every device. Free up to 10,000 pushes a month.
Create a free account →

Send your first push notification today.

Create a project, add the SDK and send with one API call. Free to start.

Create a free account →