# Telecom starter for subscriber data and segments

This guide sets up a telecom subscriber profile in Pushwoosh and turns it into working segments: bundle expiry reminders, low balance alerts, top-up confirmations, and roaming welcome messages. Complete every section and the first segment you build returns a non-zero audience, so you do not have to contact support.

The single most common mistake is the tag type. A date stored in an Integer tag looks fine in the tag list and silently makes every date segment return zero users. Choose the types first, then load the data.

## Prerequisites

* An application in your Pushwoosh account, with the SDK integrated or devices registered through the API.
* An [API access token](/developer/api-reference/api-identifiers/#api-access-token) with permission to set tags.
* Developer assistance for the server-to-server update job.
* A subscriber identifier you can map to Pushwoosh: either a User ID (usually the MSISDN, the subscriber's phone number in international format, or an internal subscriber id) or the device HWID.

## What a telecom subscriber profile looks like

The table below lists the tags that cover the standard telecom scenarios. Create them before the first data load, with exactly these types.

| Tag | Type | Example value | What it drives |
| --- | --- | --- | --- |
| `msisdn` | String | `923001234567` | Identity and SMS targeting |
| `tariff_plan` | String | `Gold Postpaid` | Plan-specific offers |
| `prepaid_postpaid` | String | `prepaid` | Splitting the base by billing model |
| `balance` | Integer | `50` | Low balance alerts |
| `bundle_id` | String | `DATA_5GB_30D` | Which bundle the reminder is about |
| `bundle_expiry_date` | Date | `2026-09-20 21:00:00` | Bundle expiry reminders |
| `roaming_status` | Boolean | `true` | Roaming welcome messages and warnings about roaming charges |

Two rows in that table decide whether the scenarios work at all.

* `bundle_expiry_date` has to be a Date tag. Only Date tags get the relative operators, such as `between N and M days ahead`, that express "the bundle expires in three days" without recalculating the segment every night.
* `bundle_id` stays separate from the expiry date. One tag holds the date, another holds which bundle it belongs to. Storing both in one tag would require parsing a string inside the segment, which the segment builder cannot do.

## Why the tag type is decided before the first upload

Tags are created automatically the first time a value arrives, and the type is inferred from that first value. A whole number becomes Integer, a number with a decimal point becomes Price, a string becomes String (or Date, if it matches a recognized date-time format such as `2024-10-02 22:11`), an array becomes List, and `true`/`false` becomes Boolean.

The inference fails on telecom data, because expiry dates are usually sent as Unix timestamps:

* You send `bundle_expiry_date` as the number `1758393600`. It is a whole number, so the tag is created as an Integer tag. Values load correctly, the tag looks healthy, and no date operator is ever offered for it.
* The tag already exists as Integer and you later switch to sending `"2026-09-20"`. The value no longer parses as a number, so it is dropped without any error. The API still answers with success, and the device keeps its old value or none at all.

Both cases end with a segment that returns zero users and no error anywhere to explain it.

A tag type cannot be changed after creation. Fixing a wrong type means creating a new tag with the correct type and reloading the values into it. The old tag stays in the list until you delete it.

To prevent both cases, set the types yourself:

1. Open the **Tags** page of your Control Panel.
2. Click **Create tag**.
3. Enter the tag name and choose its type from the list. Repeat for every tag in the table above, before the first upload.
4. In `bulkSetTags`, send `create_missing_tags: false`. A missing tag then returns an error instead of being created with a guessed type.

## How to update the profile server-to-server

Telecom profile data changes daily, so it is loaded as a batch job rather than from the mobile SDK.

1. Build the daily delta on your side: subscribers whose balance, bundle, or roaming status changed since the last run. A full base reload every night is rarely needed and costs you request volume.
2. Send the batch to [`bulkSetTags`](/developer/api-reference/audience-api/#bulksettags), addressing devices by `user_id` when the MSISDN is your User ID, or by `hwid` otherwise. One request carries many devices, and the method expects at least 50 of them. For a single subscriber, use [`setTags`](/developer/api-reference/device-api/#settags) instead.
3. Poll the returned `request_id` with [`bulkSetTags` status](/developer/api-reference/audience-api/#bulksettags-status) until the job finishes. Request it with `?detailed=true` and log the result, because a finished job is not the same as every value being accepted.
4. Retry failed batches with the same payload. Setting a tag is idempotent: sending the same value twice leaves the same profile.

```json title="Daily bundle update"
{
  "application": "XXXXX-XXXXX",
  "auth": "your API access token",
  "create_missing_tags": false,
  "devices": [{
    "user_id": "923001234567",
    "tags": {
      "bundle_id": "DATA_5GB_30D",
      "bundle_expiry_date": "2026-09-20 21:00:00",
      "balance": 50,
      "roaming_status": false
    }
  }]
}
```

### Which date formats a Date tag accepts

A Date tag stores a Unix epoch timestamp in seconds. Send one of these:

* An epoch value in seconds, as a number: `1758393600`.
* A date-time string with separators: `2026-09-20 21:00:00`, `2026-09-20 21:00`, or `2026-09-20`. A date without a time means midnight.
* An ISO 8601 string with an offset: `2026-09-20T21:00:00+05:00`.

Two formats behave in a way that surprises most integrations:

* **A string without a time zone is read as UTC.** It is not read in your local time. A bundle that expires at 21:00 in Karachi is `2026-09-20T21:00:00+05:00`, or the matching epoch value. `2026-09-20 21:00:00` is three hours earlier in real time, which moves subscribers between daily reminder waves.
* **A string of digits is an epoch value, not a date.** `"20260920"` is not September 20, 2026, it is an epoch timestamp pointing at 1970. Send either a real epoch value or a string with separators.

A value that matches none of the accepted formats is discarded without failing the request. That is why step 3 above checks the job result rather than the HTTP status alone.

## Segment recipes

Each recipe below is one segment. Open the **Segments** section, click **Create Segment** to open the builder, then add the filters listed. For the full builder walkthrough, see [Create segments by tags](/product/audience-data-and-segmentation/segmentation/create-segments/by-tags/).

### Bundle expires in three days

Targets subscribers whose current bundle runs out in three days, so the reminder arrives while a renewal still makes sense.

* **Tag:** `bundle_expiry_date`
* **Operator:** open the operator list, go to the **RELATIVE DATES** section, and choose `between N and M days ahead`
* **Values:** `3` and `3`

Change both values to `1` and `1` for the last-day reminder. Add a second filter on `bundle_id` when the message names the specific bundle.

<Aside type="caution" title="Do not use the ANNIVERSARY operators here">
The **ANNIVERSARY** section offers `is in N days`. That operator compares the day and the month only, so it matches every subscriber whose bundle expired on that date in any year. Use it for birthdays, not for bundle validity.
</Aside>

### Low balance

Targets prepaid subscribers who can no longer pay for the next renewal.

* **Tag:** `balance`, operator `less or equals`, value `50`
* **Tag:** `prepaid_postpaid`, operator `is`, value `prepaid`

Both conditions go in the same group, combined with **AND**.

### Roaming entry

Targets subscribers who are currently abroad, for a welcome message with local rates.

* **Tag:** `roaming_status`, operator `is`, value `true`

A tag-based segment reflects the state at compile time. When you need the message to go out the moment roaming starts, trigger a customer journey from a roaming event instead of sending to this segment.

### Top-up confirmation and other reactions

Confirming a top-up is a reaction to a single subscriber's action, not an audience to compile. Send a custom event from your billing system with [`postEvent`](/developer/api-reference/user-centric-api/#postevent) and start a [customer journey](/product/customer-journey/pushwoosh-journey-overview/) from it. The same applies to bundle purchase and plan change.

## The segment returns zero users

Check these in order. The first three cover most of the cases reported to support.

1. **Check the tag type on the Tags page.** If `bundle_expiry_date` is Integer, no date operator was ever applied and the segment compared numbers. Create a Date tag and reload the values.
2. **Check that values actually arrived.** Open [User Explorer](/product/audience-data-and-segmentation/user-explorer/), find a subscriber you know was in the batch, and look at their tags. An empty tag after a successful job means the values were rejected by format, most often digits-only strings or a date that no layout matched.
3. **Check the operator section.** `is in N days` under **ANNIVERSARY** ignores the year. `between N and M days ahead` under **RELATIVE DATES** does not.
4. **Check the time zone.** Expiry timestamps sent without an offset are read as UTC, which can shift a subscriber into the previous or next day of your reminder schedule.
5. **Recalculate the segment** before reading the number, so you are not looking at a cached size. See [Calculating segment size](/product/audience-data-and-segmentation/segmentation/calculating-segment-size/).

## Limitations to account for

* **A tag type is permanent.** Plan the profile before the first upload, because fixing a type later means a new tag and a full reload.
* **Relative date operators are unavailable in high-speed delivery segments.** Applications configured for [high-speed delivery](/product/resources/high-speed-delivery/) precompile their segments, and the relative date operators are not offered there. Bundle expiry reminders must run as ordinary segments.
* **A batch job is not real time.** Segments see the profile as of the last successful load. Scenarios that must fire within seconds of a balance change belong in an event-triggered journey, not in a nightly batch.