> This page is part of the [Customer.io documentation](https://docs-customerio.netlify.app). For the complete index, see [llms.txt](https://docs-customerio.netlify.app/llms.txt).
> Last updated: July 30, 2026

# Geofencing

[PremiumThis feature is available for Premium plans.](/accounts/billing/plan-features/) [EnterpriseThis feature is available for Enterprise plans.](/accounts/billing/plan-features/)

## How it works

**Geofences** let you react when a person enters or exits a real-world location—a store, a venue, a neighborhood. You define geofences in Customer.io. The SDK downloads them, monitors them on the device, and automatically sends a `Geofence Transition` event to Customer.io when your users cross into (or out of) a geofence. You can use that event to trigger journeys and build segments, the same way you use any other event.

In Customer.io, you group geofences into **geofence sets**. A geofence set is a group of geofences that you act on together. For example, you might group all of your stores into a geofence set, and trigger a campaign when a person enters any of the geofences in the set. Each `Geofence Transition` event identifies both the geofence the person crossed and the set it belongs to.

The SDK relies on the operating system’s native geofencing so transitions can arrive while your app is in the background (subject to permissions).

React Native geofencing builds on the native Android and iOS SDKs, and depends on the [Location module](/integrations/sdk/react-native/tracking/location/), which supplies the device’s current location. This tells the SDK which geofences to monitor.

 Event payloads exclude coordinates

For privacy, `Geofence Transition` events don’t include the person’s precise latitude and longitude—only whether they entered or exited a geofence.

## Prerequisites

Geofencing requires:

*   The **Location module**, which is enabled automatically when you enable geofencing. See [location tracking](/integrations/sdk/react-native/tracking/location/).
*   Location permissions from the person using your app.
*   Location services enabled on the device.
*   Google Play Services on Android devices.

## Enable geofencing in your native projects

### Android

Add the following property to your project’s `android/gradle.properties` file:

```properties
customerio_geofence_enabled=true
```

This also enables the Location module, which geofencing depends on—you don’t need to set `customerio_location_enabled` separately.

### iOS

Add the `geofence` subspec to your `ios/Podfile`:

```ruby
pod "customerio-reactnative", :subspecs => ["apn", "geofence"]
```

The `geofence` subspec includes the Location module automatically, so you don’t need to add the `location` subspec separately.

Your iOS app must include the same `Info.plist` entries required by the native iOS SDK:

```xml
<key>NSLocationWhenInUseUsageDescription</key>
<string>We use your location to send you relevant, location-based messages.</string>

<key>NSLocationAlwaysAndWhenInUseUsageDescription</key>
<string>We use your location to send you relevant, location-based messages.</string>
```

### iOS background bootstrap

iOS may relaunch your app in the background for a geofence transition before the JavaScript runtime has started. To make sure those events are processed, call `GeofenceModule.bootstrapForBackgroundDelivery()` from your `AppDelegate` in `application(_:didFinishLaunchingWithOptions:)`:

```swift
#if canImport(CioLocationGeofence)
import CioLocationGeofence
#endif

override func application(
    _ application: UIApplication,
    didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
) -> Bool {
    // ...

    #if canImport(CioLocationGeofence)
    GeofenceModule.bootstrapForBackgroundDelivery(launchOptions: launchOptions)
    #endif

    // ...
}
```

## Initialize the SDK with geofencing

Add a `geofence` object to your initialization config. Providing it opts your app into geofence monitoring and enables the [Location module](/integrations/sdk/react-native/tracking/location/) automatically—you don’t need a separate `location` config. By default, the geofence module acquires the location it needs on its own. See [location modes](#location-modes-and-monitoring) below.

```javascript
CustomerIO.initialize({
  cdpApiKey: 'your-cdp-api-key',
  region: CioRegion.US,
  geofence: {},
});
```

### Optional: background delivery on iOS

By default, when you enable geofencing, the SDK stores your `cdpApiKey` on the device so geofence events triggered from a background cold start are delivered in real time. Most apps don’t need to change this. To set it explicitly, pass an `ios` object:

```javascript
CustomerIO.initialize({
  cdpApiKey: 'your-cdp-api-key',
  region: CioRegion.US,
  geofence: {},
  ios: {
    allowBackgroundDelivery: true,
  },
});
```

When `allowBackgroundDelivery` is off, events are queued and delivered the next time your app enters the foreground.

## The Geofence Transition event

The SDK sends the `Geofence Transition` event automatically when a person enters or exits a geofence. You don’t need to track enter or exit transitions yourself.

When a person crosses a geofence, the SDK sends one event for each geofence set the geofence belongs to, each carrying that set’s `geosetId`. A geofence that isn’t in any geofence set produces a single event without a `geosetId`.

The event’s timestamp reflects when the user’s device crossed into or out of a geofence, not when the SDK delivered the event. A transition captured while the device isn’t connected to the network still lands on the timeline at the moment it occurred.

Property

Description

`transition`

The direction the device crossed the geofence boundary—`enter` or `exit`.

`geofenceId`

Customer.io-generated ID of the geofence. This is different from the `external_id` used for imports.

`geofenceName`

The geofence’s name. Included only when the geofence has one.

`geosetId`

Customer.io-generated ID of the geofence set.

`metadata`

The metadata you define on the geofence in Customer.io. Values keep their types—a numeric value arrives as a number, not a string. If the geofence has no metadata, the property is an empty object.

`transitionId`

A unique ID for the transition into or out of the geofence. Events sent for different geofence sets share the same ID, and it stays stable if the SDK retries delivery. To de-duplicate transition events, use `transitionId` and `geosetId` together.

Example event:

```json
{
  "name": "Geofence Transition",
  "timestamp": "2026-07-06T17:42:10.482Z",
  "properties": {
    "transition": "enter",
    "geofenceId": "48122",
    "geofenceName": "Downtown flagship store",
    "geosetId": "25678",
    "transitionId": "b3f1c2a4-8e7d-4c2b-9f10-2a6f1e0d5c33",
    "metadata": {
      "region": "northeast",
      "priority": 2
    }
  }
}
```

## Location modes and monitoring

Devices can only monitor a limited number of geofences at once, so the SDK monitors the geofences nearest to the user, ranked by distance from the device’s location. The SDK automatically refreshes that set as the person moves. Geofence sets don’t affect monitoring—the SDK ranks individual geofences, and geofence set membership only determines the events it sends.

The geofence module needs a location fix to select that set. The `locationMode` in your `geofence` config controls how it gets one:

Location mode

Behavior

`CioGeofenceLocationMode.Automatic` (default)

The SDK acquires a location fix on its own whenever geofencing needs one and none is already available—for example, when you identify a person.

`CioGeofenceLocationMode.Manual`

The SDK doesn’t acquire location on its own. Call `CustomerIO.geofence.refreshFromCurrentLocation()` after you identify someone and they grant location permission.

```javascript
CustomerIO.initialize({
  cdpApiKey: 'your-cdp-api-key',
  region: CioRegion.US,
  geofence: {
    locationMode: CioGeofenceLocationMode.Automatic,
  },
});
```

Location the SDK acquires for geofencing is used for geofencing only—it never produces a `Location Update` event or updates the person’s profile. It also works independently of the Location module’s tracking mode, even when location tracking is off.

`refreshFromCurrentLocation()` works in either mode—call it any time you want to force an immediate refresh. Geofences are tied to the identified person, so in manual mode call it after you identify someone; a refresh requested before you identify anyone isn’t retried automatically.

Neither mode limits detection to the moment the SDK acquires a location. Once the SDK registers geofences, the operating system monitors them natively—it wakes your app when the person crosses one, in the background and even between launches, without continuously tracking their location.

 The device won’t see new geofences immediately.

Any changes you make to geofences or geofence sets in Customer.io won’t be available on the device until it fetches updates from Customer.io. This happens within 24 hours for an active app, or sooner if the person travels a long distance, or your app forces a refresh. See [Geofence refresh](#geofence-refresh).

### Geofence refresh

The SDK re-downloads your geofence definitions from Customer.io—picking up any geofences you’ve added, changed, or removed, along with the geofence sets they belong to, when:

*   **Your app launches, or you identify a person**—at most once every 24 hours. Geofencing needs an identified profile, so if no one is identified when your app launches, the fetch waits until you identify someone.
*   **The person travels a long distance** (about 5 km by default) from where the SDK last fetched.
*   **Your app forces a refresh** by calling `refreshFromCurrentLocation()`.

So changes you make in Customer.io aren’t instant. They reach a device the next time one of these happens—typically within a day for an app in regular use, but longer for an app no one opens.

### Duplicate event suppression

The SDK suppresses duplicate enter and exit events for the same geofence within 1 hour. If a device crosses into a geofence, out again, and then crosses back into the geofence within the next hour, the SDK drops the second entrance event.

## Limitations

*   The SDK doesn’t continuously track the person’s location.
*   Geofencing depends on operating-system callbacks.
*   Background delivery depends on platform permissions.
*   Geofencing needs the device’s location services. If location is turned off—for example, airplane mode with location disabled—the operating system can’t detect crossings until location is available again. Delivery of already-captured transitions resumes when a network connection returns.
*   Android geofencing requires Google Play Services.
*   Mobile operating systems limit how many geofences an app can monitor at once. If your app uses geofencing from more than one source (for example, Customer.io and another SDK), all monitored geofences share the same operating-system limit. The Customer.io SDK manages its own geofences within that limit, but it can’t control geofences registered by other SDKs or by your app.

Version[](https://github.com/customerio/customerio-reactnative/releases/tag/6.6.1 "See this release in GitHub")

6.6.1 (Current)5.3.04.11.03.9.12.5.1

*   *   [How it works](#how-it-works)
    *   [Prerequisites](#prerequisites)
    *   [Enable geofencing in your native projects](#enable-geofencing-in-your-native-projects)
        *   [Android](#android)
        *   [iOS](#ios)
        *   [iOS background bootstrap](#ios-background-bootstrap)
    *   [Initialize the SDK with geofencing](#initialize-the-sdk-with-geofencing)
        *   [Optional: background delivery on iOS](#optional-background-delivery-on-ios)
    *   [The Geofence Transition event](#the-geofence-transition-event)
    *   [Location modes and monitoring](#location-modes-and-monitoring)
        *   [Geofence refresh](#geofence-refresh)
        *   [Duplicate event suppression](#duplicate-event-suppression)
    *   [Limitations](#limitations)

Copy page

Copy page [Download .md](/integrations/sdk/react-native/tracking/geofencing.md) [Download React Native SDK bundle](/files/react-native-sdk-docs.md)

Is this page helpful?

![](https://docs.customer.io/images/export-success.png) ![](https://docs.customer.io/images/export-failure.png)

# How can we make it better?

Close

Do you need help from Customer.io support?  No  
 Yes

What part of Customer.io do you need help with? 

How can we improve this page?

Email (optional):  Please provide a valid email address

 I am not a bot

 

We appreciate your feedback!

Our support team will contact you as soon as possible
