# Authorize
Source: https://docs.masivo.ai/api-reference/auth/authorize/get
GET /auth/authorize
Get an Access token to be able to call the api. This token should be kept secret and not exposed to the client.
# Request TOTP
Source: https://docs.masivo.ai/api-reference/auth/totp/post
POST /auth/totp
Some applications may need customers to authorize other systems on their behalf. With the 'totp' endpoint, customers can request a temporal code that other systems can use to confirm the customer identity.
# Verify TOTP
Source: https://docs.masivo.ai/api-reference/auth/totp/verify/get
GET /auth/totp/verify
Verify the TOTP token, if valid return matching customer
# Get brands
Source: https://docs.masivo.ai/api-reference/brands/get
GET /brands
Get all brands
# Register customer
Source: https://docs.masivo.ai/api-reference/customers/post
POST /customers
Register a customer. For anonymous customers, only the `id` and `is_anonymous: true` are required. Anonymous customers can later be identified using the `/customers/{id}/identify/anonymous` endpoint.
# Introduction
Source: https://docs.masivo.ai/api-reference/introduction
Learn how to integrate your services with Masivo's REST API
## Welcome to Masivo
If you are looking to make your customers accumulate & redeem rewards using Masivo, you are in the right place. This API reference will help you understand how to integrate your services with Masivo's REST API.
Get started by creating and setting up an account on Masivo to get an API key.
## Authentication
Masivo uses OAuth to authenticate request. First you need to get your account's API key from the dashboard. Go to **Settings -> Advance settings** and copy the API key.
You will need to use the API key to request an **access token**. All requests to the API should include the access token in the `Authorization` header.
Your API Key should never be exposed in a client facing application. Keep it
secure in your services.
Remember that access tokens expires after 1 hour. You will need to request a new access token after it expires.
See our starter guide [Integrating with Masivo's API](/api-reference/guides/integrate-with-masivo) to use our authentication snippet.
# Get platforms
Source: https://docs.masivo.ai/api-reference/platforms/get
GET /platforms
Get all platforms
# Get reward
Source: https://docs.masivo.ai/api-reference/rewards/[id]/get
GET /rewards/{id}
Get a specific reward
# Get rewards
Source: https://docs.masivo.ai/api-reference/rewards/get
GET /rewards
Get all rewards. Rewards are created and managed from the dashboard; there is no API endpoint to create one. To reference a reward as a rule effect's reward_id when creating a campaign, first create it from the dashboard, then look up its id here.
# Get customer gift cards
Source: https://docs.masivo.ai/api-reference/rewards/gift-cards/get
GET /customers/{id}/rewards/gift-cards
Get customer gift cards
# Redeem gift card
Source: https://docs.masivo.ai/api-reference/rewards/gift-cards/redeem/delete
Delete /rewards/gift-cards/redeem
Mark a gift card as redeemed
# Get tiers
Source: https://docs.masivo.ai/api-reference/tiers/get
GET /tiers
Get all tiers
# Automate customer lifecycle
Source: https://docs.masivo.ai/case/automate-customer-lifecycle
Learn how to automate the customer lifecycle using Masivo
This page is a work in progress. Feel free to check back later to see if it’s
available.
# Get to know your customers
Source: https://docs.masivo.ai/case/get-to-know-your-customers
Masivo is a powerful tool to get to know your customers better. Learn how to use it to improve your business.
This page is a work in progress. Feel free to check back later to see if it’s
available.
# Improve RFM score
Source: https://docs.masivo.ai/case/improve-rfm-score
Learn how to improve your RFM score using Masivo
This page is a work in progress. Feel free to check back later to see if it’s
available.
# Increase customer engagement
Source: https://docs.masivo.ai/case/increase-customer-engagement
Using Masivo you can increase customer engagement, learn how to use it to improve your business.
This page is a work in progress. Feel free to check back later to see if it’s
available.
# Reduce customer churn
Source: https://docs.masivo.ai/case/reduce-customer-churn
Learn how to reduce customer churn using Masivo
This page is a work in progress. Feel free to check back later to see if it’s
available.
# Segment customers
Source: https://docs.masivo.ai/case/segment-customers
Learn how to segment your customers using Masivo
This page is a work in progress. Feel free to check back later to see if it’s
available.
# Comparison
Source: https://docs.masivo.ai/comparison
Compare Masivo's features and capabilities with other loyalty platforms
## Masivo vs. Marigold Cheetah Digital: A Comparative Overview
While both Masivo and Marigold Cheetah Digital aim to enhance customer engagement and drive sales, each platform offers unique strengths tailored to different business needs. Here's how Masivo stands out in its simplicity and effectiveness compared to Marigold Cheetah Digital:
### 1. Focus
**Masivo:** Masivo prioritizes simplicity and effectiveness in its approach to customer engagement. It specializes in running straightforward and highly effective campaigns that leverage rewards such as loyalty points and discounts to incentivize customer actions. This focused approach ensures that businesses can quickly implement and see results from their marketing efforts.
**Marigold Cheetah Digital:** Marigold Cheetah Digital offers a comprehensive suite of marketing solutions, including email marketing, loyalty programs, and personalized messaging. While this breadth may be suitable for larger enterprises with diverse marketing needs, it can also introduce complexity that may not be necessary for businesses seeking a more streamlined solution.
### 2. Campaigns and Rewards
**Masivo:** Masivo excels in simplicity and effectiveness when it comes to running campaigns and managing rewards. Its user-friendly interface and straightforward campaign setup process make it easy for businesses to launch targeted campaigns that drive results. By focusing on rewards-based incentives like loyalty points and discounts, Masivo ensures that businesses can quickly engage and retain customers without unnecessary complexity.
**Marigold Cheetah Digital:** While Marigold Cheetah Digital also offers campaign management and rewards functionality, its broader range of marketing features may introduce complexity that some businesses may find overwhelming. While suitable for enterprises with diverse marketing needs, this complexity can hinder the effectiveness and simplicity that Masivo offers.
### 3. Integration and Customization
**Masivo:** Masivo's simplicity extends to its integration and customization capabilities. As a headless service, it seamlessly integrates with existing storefront applications, allowing businesses to tailor the customer experience to their specific needs and branding preferences without unnecessary complexity.
**Marigold Cheetah Digital:** While Marigold Cheetah Digital offers integration capabilities, its broader range of features may require more extensive customization efforts to achieve the desired results. This complexity may pose challenges for businesses seeking a simple and effective solution like Masivo.
In summary, Masivo stands out for its simplicity and effectiveness in driving customer engagement and sales through straightforward campaigns and rewards management. While Marigold Cheetah Digital offers a broader range of marketing solutions, businesses seeking a streamlined approach will find Masivo's simplicity and focus on effectiveness to be highly appealing.
# Brands
Source: https://docs.masivo.ai/concepts/brands
Learn what brands are and how they can help you create a successful loyalty program.
You may manage several brands that need to use the same loyalty program. Brands help you distinguish which brand is offering a reward to a customer. This is useful for tracking how much each brand is spending on rewards and how much each brand is earning.
Each brand can have its own rewards and campaigns if desired, ensuring that rewards are only given to the intended brand. Alternatively, you can share rewards and campaigns between brands.
# Campaigns
Source: https://docs.masivo.ai/concepts/campaigns
Learn what campaigns are and how they can help you create a successful loyalty program.
Campaigns are how you distribute rewards to your customers. They also allow you to track your spending on rewards and determine the scenarios in which a customer is eligible to receive them.
Some examples of campaigns are:
* Double points on purchases made on Tuesdays.
* 2x1 on all products in the store.
* Gold tier customers get a 10% discount on their next purchase.
To learn how to create a campaign, see our [Launch a campaign guide](/guides/launch-a-campaign).
## Campaigns in Masivo
* Each campaign can be configured to offer any amount of rewards to any customer following the rules you setup.
* Rewards offered in a campaign can set their own expiration and how many they will be distributed per event.
* Campaigns must offer at least one reward per rule.
* Masivo will track how many rewards were distributed and how many were redeemed.
* You can set budgets to control how much you spend on rewards redemption.
## Tier eligibility
The **Tiers** field in E-commerce is a global eligibility scope. When tiers are
selected, only customers whose current tier is included can earn from the
campaign. Leaving it empty allows every tier and customers without a tier.
This scope is applied before campaign rules. Coupon campaigns can also use the
singular **Customer tier** condition inside a rule; when both are configured,
both restrictions must match. Eligibility uses the customer's tier at the start
of event processing, before any tier transition caused by that event.
## Types of campaigns
Similarly to rewards, you can create different types of campaigns. Currently we support:
1. [Behavior](/concepts/campaigns/#behavior)
2. [Coupons](/concepts/campaigns/#coupons)
3. [Punch cards](/concepts/punch-cards)
4. [Referrals](/concepts/referrals)
Specialized campaign types with dedicated guides:
* [Score Streaks](/concepts/score-streaks) — reward consistent behavior with milestone tiers and time windows
### Behavior
Behavior campaigns are campaigns that are triggered by a customer event. For example, a purchase, filling out a survey, or signing up to your platform.
You can send any event you would like. Masivo will track the event and apply the rules you set to distribute the rewards. Only matching events will trigger the campaign.
### Coupons
Coupons campaigns are campaigns that are triggered by a customer redeeming a coupon code. You can create a coupon codes and set the rules for it to be redeemed. When a customer redeems the coupon, the campaign will be triggered and the rewards will be accumulated in the customer's wallet.
### Punch cards
Punch card campaigns are digital stamp cards triggered by a customer event. See [Punch Cards](/concepts/punch-cards) for punches, card limits, and per-punch rewards.
### Referrals
Referral campaigns let customers invite others with a referral code. See [Referrals](/concepts/referrals) for codes, referrer vs referee rewards, and use limits.
## Campaigns comparison
| | Flexible | Ease of use | Preferred by customers |
| - | - | - | - |
| [Behavior](/concepts/campaigns/#behavior) | ✅ | ✅ | |
| [Coupons](/concepts/campaigns/#coupons) | | ✅ | ✅ |
| [Punch cards](/concepts/punch-cards) | | ✅ | ✅ |
| [Referrals](/concepts/referrals) | ✅ | | ✅ |
# Catalogs
Source: https://docs.masivo.ai/concepts/catalogs
Store product or content rows for personalization and selections.
Catalogs hold tabular data (items with an `id` and custom fields) per account. Create catalogs from the dashboard using your e-commerce products, or manage catalogs and items through the Storefront API. Define **selections** with filters, sort, and a results limit.
## Limits
* Storage: 500 MB total catalog item payload per account.
* Up to 1000 catalogs per account.
* Selections: up to 30 per catalog, 10 filters each, results limit 1–50.
# Channels
Source: https://docs.masivo.ai/concepts/channels
Learn what channels are and how they can help you create a successful loyalty program.
Channels are the means through which you distribute your goods and services to customers. You can synchronize your e-commerce channels with Masivo to track customer behavior and create advanced filtering rules. For example, you can set up a campaign that rewards customers who make a purchase through a specific channel.
# Customers
Source: https://docs.masivo.ai/concepts/customers
Learn what customers are in Masivo.
Customers are the people who interact with your business. However Masivo only counts as customers those who have signed up to your platform. This allows you to track their behavior and reward them for their loyalty.
Learn how to register a customer in our [Tracking customer activity guide](/guides/track-customer-activity).
# Customer events
Source: https://docs.masivo.ai/concepts/events
Learn what events are in Masivo.
Events are the actions that your customers take that trigger certain campaigns of type behavior. For example, a purchase, filling out a survey, or signing up to your platform. You can send any event you would like. Masivo will track the event and apply the rules you set to distribute the rewards. Only matching events will trigger certain campaigns.
When a customer submits a [review](/concepts/review-forms) through the Storefront API, Masivo creates a resolved **`REVIEW`** event linked to that submission. Use it to track feedback alongside purchases and other behavior events.
# In-app messages
Source: https://docs.masivo.ai/concepts/in-app-messages
Deliver personalized messages inside your mobile app through templates, journeys, and the Storefront API.
In-app messages let you show modals, banners, and bottom sheets inside your mobile app. Templates are created in the Masivo dashboard; your app fetches eligible messages and reports impressions through the Storefront API.
## How in-app messages work
```mermaid theme={null}
flowchart LR
T[Template created] --> M[Message materialized]
M --> F[App fetches eligible messages]
F --> S[App shows message]
S --> E[App logs shown / clicked / dismissed]
E --> A[Analytics & tracking events]
```
Messages are **materialized per customer** when a template is scheduled to an audience, sent as a test, or delivered through a journey. Each row in `inapp_messages` stores the rendered content, display rules, and delivery metadata.
Your mobile app:
1. Calls `GET /inapp/capabilities` to know whether in-app is enabled and which journey event types support real-time delivery.
2. Calls `GET /customers/{id}/inapp-messages` after app open or when a trigger event fires.
3. Renders the message using the `type` and `content` fields.
4. Calls `POST /customers/{id}/inapp-messages/{messageId}/events` for each impression action.
## Message lifecycle
```mermaid theme={null}
stateDiagram-v2
[*] --> pending: Message materialized
pending --> shown: shown event (under cap)
pending --> clicked: clicked event
pending --> dismissed: dismissed event
shown --> pending: shown event (more impressions left)
shown --> clicked: clicked event
shown --> dismissed: dismissed event
pending --> expired: expires_at passed
clicked --> [*]
dismissed --> [*]
expired --> [*]
```
| Status | Description |
| - | - |
| `pending` | Eligible for display; may still accept more `shown` events if under `max_impressions` |
| `shown` | Last action was `shown` and impression cap was reached |
| `clicked` | Customer tapped a button or primary action |
| `dismissed` | Customer closed the message |
| `expired` | `expires_at` is in the past |
## Message types
| Type | Use case |
| - | - |
| `modal` | Centered dialog over the current screen |
| `fullscreen` | Full-screen takeover |
| `slideup` | Bottom sheet style panel |
| `banner` | Top or bottom banner |
## Template configuration
| Field | Type | Description |
| - | - | - |
| `name` | `string` | Unique template name per account |
| `message_type` | enum | One of the message types above |
| `content.title` | `string` | Headline text |
| `content.body` | `string` | Main copy (required in dashboard) |
| `content.image_url` | `string` | Optional hero image URL |
| `content.buttons` | array | Primary, dismiss, and deeplink buttons |
| `display_rules.max_impressions` | `number` | Max times a customer can see the message (default 1) |
| `display_rules.min_interval_seconds` | `number` | Minimum gap between `shown` events |
| `display_rules.priority` | `number` | Higher values win when multiple messages are eligible |
| `team_ids` | `string[]` | Restrict analytics visibility to assigned teams |
| `variants` | array | Up to 4 optional content versions. Journey actions must pick one when more than one exists. Scheduled sends use a weight distribution that sums to 100%. |
## Delivery sources
| Source | Trigger |
| - | - |
| `scheduled` | Audience schedule from the dashboard |
| `test` | Test send to a selected audience |
| `journey` | Journey node with an in-app action |
Journey-connected templates can also trigger **real-time delivery**: when a qualifying event is emitted, Masivo materializes the message and sends a silent push to customers with a registered device token and push consent. Your app should handle that payload and refresh via `GET /customers/{id}/inapp-messages`. Use `GET /inapp/capabilities` to read the supported `eventTypes`.
## Tracking events
Each impression action emits a resolved tracking event:
| Action | Event type |
| - | - |
| `shown` | `INAPP_SHOWN` |
| `clicked` | `INAPP_CLICKED` |
| `dismissed` | `INAPP_DISMISSED` |
Pass optional `metadata` (for example `button_id`) on click events for button-level analytics.
## Comparison with push notifications
| | In-app messages | Push notifications |
| - | - | - |
| Surface | Inside the app UI | OS notification tray |
| Requires device token | Only for real-time silent push (journeys); not for on-open fetch | Yes |
| Rich layout | Full templates (modal, banner, slideup, fullscreen) | Title, body, image, deeplink |
| Real-time trigger | Journey events + silent push refresh | FCM/APNs delivery |
| Impression tracking | Explicit API events | Open/delivery analytics |
## Related documentation
* [In-app messages](/guides/create-in-app-templates) — dashboard setup
* [Template variants](/guides/template-variants) — A/B testing on templates
* [Integrate in-app messages](/guides/in-app-messages) — end-to-end app integration
* [Integrate in-app messages](/api-reference/guides/integrate-in-app-messages) — Storefront API reference guide
# Index
Source: https://docs.masivo.ai/concepts/index
Learn about the main concepts behind Masivo and how they can help you create a successful loyalty program.
Rewards are digital assets that can be redeemed by customers
A distribution & tracking mechanism for rewards
Reward consistent behavior with milestone streaks
Digital stamp cards with rewards on specific punches
Invite codes that reward referrers and referees
Personalized messages inside your mobile app
Reusable questionnaires and structured feedback via API
Reward your most loyal customers with exclusive benefits
Register & manage your customers
A digital wallet to store rewards
Events can be use to trigger some campaigns & track customer behavior
Keep a record of all rewards distributed & redeemed
Manage multiple brands under a single loyalty program
Integrate with your e-commerce products
Integrate with your e-commerce stores
Integrate with your e-commerce distribution channels
Organize members and scope analytics visibility
Operational custom-event archiving pipeline
# News Feed
Source: https://docs.masivo.ai/concepts/news-feed
Persistent in-app cards resolved from live templates for eligible customers.
News Feed cards are persistent in-app content. Unlike overlay in-app messages, a card remains eligible until its assignment expires or the customer dismisses it.
## How News Feed works
Cards are assigned to one customer, to an audience, or globally. The Storefront API resolves those assignments against the active template every time the feed is requested.
```mermaid theme={null}
flowchart LR
T[Active template] --> A[Journey or scheduled assignment]
A --> E{Eligible now?}
E -- No --> Skip[Omitted]
E -- Yes --> F[GET customer news feed]
F --> R[Resolve live template content]
R --> UI[Render in the app]
UI --> I[Log shown, clicked, or dismissed]
I --> S[Customer read state]
```
Audience assignments are evaluated against current audience membership. If a customer leaves the audience, that assignment no longer makes the card eligible. Editing an active template changes the content returned for every existing assignment because assignments store a template reference rather than a content copy.
## Card lifecycle
```mermaid theme={null}
stateDiagram-v2
[*] --> Assigned: Journey or scheduled publication
Assigned --> Visible: Eligible feed request
Visible --> Visible: shown or clicked
Visible --> Dismissed: dismissed
Assigned --> Expired: expires_at reached
Dismissed --> Visible: newer assignment created
Expired --> [*]
Dismissed --> [*]
```
| State | Stored signal | Behavior |
| - | - | - |
| Assigned | `news_feed_by_customer` row | Targets a customer, audience, or the whole account |
| Visible | Active assignment and active template | Returned by `GET /customers/{id}/news-feed` |
| Shown | `shown_at` | Records an impression without removing the card |
| Clicked | `clicked_at` | Records a click without removing the card |
| Dismissed | `dismissed_at` | Hidden until a newer assignment of the same template exists |
| Expired | `expires_at` is in the past | Excluded from the feed and later deleted by cleanup |
Tracking emits `NEWS_FEED_ASSIGNED`, `NEWS_FEED_SHOWN`, `NEWS_FEED_CLICKED`, and `NEWS_FEED_DISMISSED` events.
## Configuration
| Field | Type | Allowed values | Description |
| - | - | - | - |
| `name` | `string` | Non-empty | Internal template name |
| `status` | `string` | Account status values | Only `ACTIVE` templates are returned |
| `description` | `string` | Optional | Internal description |
| `brand_id` | `string \| null` | Optional | Restricts the card to a brand |
| `campaign_id` | `uuid \| null` | Optional | Associates analytics with a campaign |
| `card_type` | `string` | `classic`, `captioned_image`, `image_only` | Controls which content fields the editor requires |
| `content_title` | `string` | Required for classic and captioned image | Card title |
| `content_body` | `string` | Required for classic and captioned image | Card message |
| `content_image_url` | URL | Required for captioned image and image only | Card image or classic icon |
| `content_link_url` | URL | Optional | Destination opened by the card |
| `content_link_text` | `string` | Optional | Link label |
| `content_pinned` | `boolean` | `true`, `false` | Places the card before unpinned cards |
| `priority` | `integer` | Optional | Orders cards within the pinned group |
| `expiration_days` | `integer` | `1`–`30` | Duration calculated from assignment creation |
| `team_ids` | `string[]` | Up to 20 ids | Limits dashboard access to teams |
| `news_feed_default_expiration_days` | `integer` | `1`–`30` | Account fallback when a template has no expiration |
| `variants` | array | Up to 4 | Optional A/B content versions. Journey actions must pick one when more than one exists. Scheduled sends use a weight distribution that sums to 100%. |
The public feed endpoint accepts `limit` from 1 to 50 for each request; there is no separate account-level feed size setting.
## Eligibility and ordering
The feed combines three assignment branches:
1. Direct customer assignments, including journey actions and test sends.
2. Global assignments with no customer or audience target.
3. Audience assignments when the customer is currently a member.
Duplicate branches for the same template are collapsed. A non-expiring assignment keeps the combined card non-expiring. Cards are ordered by pinned state, numeric priority, and newest assignment time.
## Operations and cleanup
`POST /api/cron/marketing_automation/news_feed/cleanup` deletes expired assignments in batches of 5,000. It also deletes stale read-state rows after a seven-day grace period when no direct, global, or current-audience assignment still covers the customer and template.
Template expiration changes enqueue a batched recomputation of `expires_at` for existing assignments.
## Comparison with in-app messages
| | News Feed | In-app messages |
| - | - | - |
| Surface | Persistent feed | Overlay, banner, modal, or custom code |
| Storage | Assignment references a live template | Per-customer message rows |
| Audience membership | Resolved live | Materialized at delivery time |
| Dismissal | Hidden until a newer assignment | Message state becomes dismissed |
| Public fetch | `/customers/{id}/news-feed` | `/customers/{id}/inapp-messages` |
| Interaction endpoint | Uses template id | Uses message id |
# Products
Source: https://docs.masivo.ai/concepts/products
Learn what products are in Masivo.
Products are the physical goods and services you sell. You can synchronize your e-commerce products with Masivo to create advanced filtering rules. For example, you can set up a campaign that rewards customers whose orders include a specific product.
With Products you can also offer them as rewards in your campaigns. For example, you can create a campaign that rewards customers with a free product when they spend a certain amount of money.
# Service forms
Source: https://docs.masivo.ai/concepts/review-forms
Learn what service forms are in Masivo and how to collect structured feedback through the API.
Service forms let you define reusable questionnaires in the Masivo dashboard and collect answers from your apps, websites, or integrations through the Storefront API. Each form stores its field schema in Masivo; your client renders the UI and submits reviews.
The dashboard calls them **service forms**. Existing API paths and permission keys keep the technical `review-forms` and `review_forms` names for compatibility.
Common use cases include post-purchase ratings, NPS-style surveys, comment threads, and custom feedback flows.
To create and manage service forms in the dashboard, see the [Create and manage service forms guide](/guides/create-and-manage-review-forms). To integrate programmatically, see [Submit reviews](/api-reference/guides/submit-reviews).
## Form types vs field types
**Form type** describes the overall intent of the review form. It is metadata for your team and does not change API validation by itself:
| Form type | Typical use |
| - | - |
| `RATING` | Star or numeric satisfaction surveys |
| `COMMENT` | Open feedback |
| `THREAD` | Multi-step or conversational flows |
| `CUSTOM` | Any other structured feedback |
**Field types** define how each answer is validated when a review is submitted. Answers are always keyed by the field **`name`** (not an internal id).
| Field type | Answer shape | Validation |
| - | - | - |
| `SCORE` | Number | Between `min` and `max` (defaults: 1–5) |
| `TEXT` | String | Free text |
| `SELECT` | String | Must match one `options[].value` |
| `MULTI_SELECT` | Array of strings | Each value must be in `options[].value` |
## Field images
Each field can include up to **5 images** (for example, icons for a score scale). Images are stored as public URLs from your account **multimedia** library, plus an optional `alt` text for accessibility.
## Lifecycle and visibility
| Status | Dashboard | Storefront API |
| - | - | - |
| `ACTIVE` | Visible | `GET /review-forms/{id}` returns the review form |
| `INACTIVE` | Visible | Treated as not found (`404`) |
Deleting a review form in the dashboard performs a soft delete and sets the form to inactive.
## Ticket automation
Service forms can open a support ticket from the responses they receive. Enable **Ticket automation** in the form settings, pick the pipeline and priority the generated tickets should use, and decide which responses deserve a ticket.
| Setting | Default | Description |
| - | - | - |
| `enabled` | `false` | Turns the automation on for this form |
| `pipeline_id` | — | Explicit pipeline for generated tickets; required when automation is enabled |
| `priority` | `medium` | Priority applied to every generated ticket |
| `trigger` | `always` | `always` opens a ticket per response; `conditions` filters them |
| `conditions` | `[]` | Condition groups evaluated against the response |
The ticket title comes from the form name and the description is a recap of the submitted answers. The response is linked to the ticket, so agents can open the original submission from the ticket detail.
### Conditions
Conditions use the same rule set as campaigns, rewards, and journeys: `conditions` is a list of groups, every condition inside a group must match (**AND**), and any group matching is enough to open the ticket (**OR**).
| Condition type | Reads | Operators |
| - | - | - |
| `Response answer` | The answer named in `field` | Depend on the field type, see below |
| `Response sentiment score` | The AI sentiment score of the response (0-100) | `greater than`, `less than`, `equal to`, and their variants |
| `Response emotion` | The emotions the AI detected in the response | `is` (detected), `is not` (not detected) |
| `Response risk flag` | The risk flags the AI detected in the response | `is` (detected), `is not` (not detected) |
`Response answer` is only offered when the form has at least one field, and the operators follow the type of the answer you pick:
| Field type | Operators | Value |
| - | - | - |
| `SCORE` | `greater than`, `greater than or equal to`, `less than`, `less than or equal to`, `equal to`, `not equal to` | Number between the field `min` and `max` |
| `TEXT` | `equal to`, `not equal to`, `contains`, `starts with`, `ends with` | Free text |
| `SELECT`, `MULTI_SELECT` | `equal to`, `not equal to` | One of the configured options |
Answers that arrive empty never match, so a missing field cannot open a ticket on its own. On multi-select answers, `equal to` matches when the value is one of the selected options. Text comparisons ignore letter casing.
```json theme={null}
{
"enabled": true,
"priority": "high",
"trigger": "conditions",
"conditions": [
[
{
"primitive": "custom",
"type": "Response answer",
"field": "score",
"operator": "less than or equal to",
"value": "2"
}
],
[
{
"primitive": "custom",
"type": "Response risk flag",
"operator": "is",
"value": "churn_risk"
}
]
]
}
```
Answer conditions are evaluated as soon as the response is stored. Conditions that read the AI analysis (sentiment, emotions, risk flags) are evaluated when the review analysis job processes the response, so those tickets appear a few minutes later. A group whose answer conditions already fail is discarded without waiting for the analysis, and a group that fully matches on answers opens the ticket right away.
Ticket creation runs after the response is stored: if the pipeline or the routing is misconfigured, the response is still saved and the API keeps returning `201`.
New tickets are assigned to the available agent with the lightest open load, respecting each agent's maximum open tickets. Accounts without a configured agent roster get unassigned tickets.
## Events and analytics
Every successful review submission creates a resolved **`REVIEW`** [customer event](/concepts/events) linked to the review. You can use `source`, `platform`, `brand_id`, `store_id`, and `channel_id` on the review for attribution, similar to other storefront activity.
Anonymous reviews are supported: omit `customer_id` in the API request. The review row stores `customer_id` as `null`, while the linked event uses the review id as the customer identifier for tracking.
## Review analysis operations
Masivo can process submitted reviews with an internal AI cron route and store validated review analysis in the `reviews.review_analysis` JSON column. The job is not trigger-based. It is designed to be scheduled externally, typically every five minutes, and processes up to 50 reviews per run using a Redis cursor.
The processor:
* reads new review rows from `reviews`
* analyzes answer content, including score-only reviews
* validates strict JSON output against the configured analysis catalog
* stores `pending`, `processed`, or `failed` status per review
* updates the source review row, including `updated_at`, so review backfills can sync AI changes
* records AI validation errors for debugging
New responses take priority in each run. The remaining slots go to reviews that need another attempt: those left in `failed`, and those stuck in `pending` because a previous run was interrupted. Each attempt increases `retryCount`, and a review stops being picked up after 3 of them, so a permanently broken response cannot block the queue. A successful analysis resets `retryCount` to 0. Because ticket automation runs right after the analysis, recovering a failed analysis also recovers the ticket that was waiting for it.
The operational route is:
`POST /api/cron/analytics/review-analysis`
It requires the standard `x-masivo-cron-auth` header. Passing `force: true` resets the Redis cursor so processing starts from the beginning again.
## Permissions
Access to the Service forms section in the dashboard is controlled by the **`review_forms`** role resource:
* **read** — view service forms and reviews
* **write** — create and edit service forms
* **delete** — delete service forms
Configure permissions under **Settings → Roles**.
# Rewards
Source: https://docs.masivo.ai/concepts/rewards
Learn what rewards are and how they can help you create a successful loyalty program.
Rewards are the incentives you offer to your customers to encourage them to engage with your loyalty program. You can use rewards in many ways to drive customer behavior and increase customer lifetime value.
To learn how to create a reward, see our [Creating a reward guide](/guides/create-rewards).
## Rewards in Masivo
* Rewards in Masivo are distributed through campaigns.
* The amount of rewards and expiration date can be set in the campaign.
* Every reward may contain conditions that must be met to be redeemed.
* You can create as many rewards of any type as you need.
* All rewards are accumulated in the customer's wallet.
## Tier eligibility
The **Tiers** field in E-commerce controls who can redeem a reward. Leaving it
empty allows customers in any tier, including customers without a tier. When
tiers are selected, the customer's current tier must be included.
Rewards stored in wallets are snapshots: changing a reward tier scope affects
an existing wallet reward only after the existing wallet update flow replaces
that snapshot.
## Types of rewards
In Masivo, you can create different types of rewards. Currently we support:
1. [Points](/concepts/rewards/#points)
2. [Discounts](/concepts/rewards/#discounts)
3. [Gift cards](/concepts/rewards/#gift-cards)
4. [Products](/concepts/rewards/#products)
There will be other types of rewards in the future, so stay tuned!
### Points
Points are usually given for purchases or other actions you want to encourage. Customers can accumulate these points and redeem them for rewards.
With Masivo, you can decide how much each point is worth. For example, you can make 1 point equal to \$0.10.
Sometimes, you might want to have more than one currency of point. This is helpful if you want to make some rewards more special than others. One type of point might be easier to earn than the other.
### Discounts
Discounts are a great way to encourage customers to make a purchase. You can offer discounts in many ways, such as:
#### By criteria
* Order value
* Product in order
* Discounted shipping
#### By value
* Percentage off
* Fixed amount off
#### By condition
* Purchase in given store
* Purchase in given channel
* Purchase in given time
* Purchase higher than given amount
### Gift cards
Gift cards are another great way to encourage customers to make a purchase. Gift cards are typically used as a gift for someone else, this promote your brand to new customers.
In Masivo each gift card contains a code that can be redeemed in physical stores. You can configure as many gift card codes as you need. Just make sure your physical store knows how to use those codes.
### Products
Don't confuse Product rewards with products. Your e-commerce products are use
as rewards in rewards of type product.
Product rewards are a great way to promote new products or clear out old stock. You can offer product rewards in two ways:
1. **Adding a product to the order** - When you want to give a free product to the customer.
2. **Replacing a product in the order** - When you want to replace a product in the order with a cheaper version of the same item, or if you offer the original product bundled with another for the same price.
Product rewards may have conditions that must be met to be redeemed. This is useful when you would like to offer a product reward only if the customer buys a specific product or spends a certain amount. For example:
* Buy a pair of shoes and get a free pair of socks.
* Spend \$100 and get a free t-shirt.
* 2x1 on Tuesdays.
## Rewards comparison
| | Cheap | Flexible | Ease of use | Preferred by customers |
| - | - | - | - | - |
| [Points](/concepts/rewards/#points) | ✅ | ✅ | | |
| [Discounts](/concepts/rewards/#discounts) | | ✅ | ✅ | ✅ |
| [Gift cards](/concepts/rewards/#gift-cards) | ✅ | ✅ | ✅ | |
| [Products](/concepts/rewards/#products) | | | | ✅ |
# Score Streaks
Source: https://docs.masivo.ai/concepts/score-streaks
Reward customers for consistent behavior by building streaks that unlock milestone rewards.
Score Streaks are a campaign type that tracks how consistently a customer repeats a specific action over time. Each qualifying event extends the streak and increases the customer's score. When the score reaches a configured milestone, rewards are automatically granted.
## How score streaks work
A score streak campaign tracks three layered ideas: **time windows (periods)**, **consecutive activity (streaks)**, and **cumulative score (milestones)**.
```mermaid theme={null}
flowchart LR
E[Customer event] --> S{Qualifies?}
S -- No --> Skip[Ignored]
S -- Yes --> P{Period expired?}
P -- Yes --> Loss[Cycle lost\nNew cycle starts]
P -- No --> Gain[Score +1\nPeriod extended]
Loss --> Gain
Gain --> M{Milestone\ncrossed?}
M -- Yes --> Reward[Reward granted]
M -- No --> Done[Done]
```
### Periods and loss conditions
Every time a customer earns a score point, the campaign opens a **period window** — a countdown timer during which the customer must send the next qualifying event to keep the streak alive. If the timer runs out before the next event, the cycle is **lost**: the streak resets to zero and a new cycle begins.
The loss window is defined by `loss.conditions[0]`:
```json theme={null}
{
"type": "NO_TRIGGER_EVENT_WITHIN",
"amount": 1,
"unit": "DAYS"
}
```
Supported units: `MINUTES`, `HOURS`, `DAYS`, `WEEKS`, `MONTHS`.
### Cycles
A **cycle** is one continuous run of activity without a loss. Each loss or reset starts a fresh cycle identified by a new `current_cycle_id`. This means multiple cycles can exist in a single streak record over its lifetime, and the history events are stamped with the cycle they belong to.
### Milestones
Rules in a score streak campaign define **score thresholds** that trigger rewards. When a customer's `current_score` reaches or exceeds a milestone, that milestone's effects (rewards) are granted once per milestone per cycle.
```
Score 1: Milestone reward (first milestone)
Score 5: Higher milestone reward
Score 10: Highest milestone reward
Score 11: Highest milestone reward again (repeats)
```
If the score crosses multiple milestones at once (e.g. from 0 to 5), only the highest applicable milestone fires.
### Scheduled resets
Optionally, a campaign can define a **reset calendar** that forcefully closes the active cycle on a schedule — daily, weekly, or monthly. This is independent of the loss condition: a customer with a perfect streak will still be reset on the configured date.
```json theme={null}
{
"enabled": true,
"calendar": {
"frequency": "MONTHLY",
"day_of_month": 1
}
}
```
## Score streak lifecycle
```mermaid theme={null}
stateDiagram-v2
[*] --> Active: First qualifying event
Active --> Active: Qualifying event within period
Active --> Lost: Period expired (cron)
Lost --> Active: Next qualifying event
Active --> Reset: Scheduled reset date
Reset --> Active: Next qualifying event
Active --> Closed: Campaign ended
Closed --> [*]
```
| State | Description |
| - | - |
| `ACTIVE` | Customer has an active streak in progress |
| `CLOSED` | Campaign ended; streak is archived |
History events stored in the `events` JSON column:
| Type | Trigger |
| - | - |
| `GAIN` | Qualifying event processed; score incremented |
| `MILESTONE` | Score crossed a milestone threshold |
| `LOSS` | Period expired without a qualifying event |
| `RESET` | Scheduled reset fired |
| `MANUAL_RESET` | Support agent triggered a manual reset |
| `EVENT_REVERSAL` | A source event was reversed, rolling back the associated score gain |
## Campaign configuration
| Field | Type | Description |
| - | - | - |
| `trigger` | `string` | Event type that increments the score (e.g. `"PURCHASE"`) |
| `privacy` | `"PRIVATE" \| "PUBLIC"` | Controls visibility in public-facing APIs |
| `loss.conditions[0].type` | `"NO_TRIGGER_EVENT_WITHIN"` | Always this value |
| `loss.conditions[0].amount` | `number` | How many units before the streak is lost |
| `loss.conditions[0].unit` | `"MINUTES" \| "HOURS" \| "DAYS" \| "WEEKS" \| "MONTHS"` | Time unit |
| `reset.enabled` | `boolean` | Whether scheduled resets are active |
| `reset.calendar.frequency` | `"DAILY" \| "WEEKLY" \| "MONTHLY"` | Reset cadence |
| `reset.calendar.week_starts_on` | `0–6` | First day of week (0 = Sunday). Required when `WEEKLY` |
| `reset.calendar.day_of_month` | `1–31` | Day of month for reset. Required when `MONTHLY`. Clamped to last day for short months |
## Milestone rules
Each rule in a score streak campaign must have a `milestone` score value. When the customer's score reaches that value within a cycle, the rule's effects are applied. Multiple rules can define different reward tiers:
```json theme={null}
{
"rules": [
{
"milestone": 1,
"conditions": [],
"effects": [{ "type": "POINTS", "amount": 10 }]
},
{
"milestone": 5,
"conditions": [],
"effects": [{ "type": "POINTS", "amount": 50 }]
},
{
"milestone": 10,
"conditions": [],
"effects": [{ "type": "POINTS", "amount": 200 }]
}
]
}
```
* Conditions in milestone rules are ignored; only `milestone` and `effects` matter.
* When the score reaches milestone 10, only that rule fires — the lower milestones (1, 5) do not fire again for the same score.
* If a score crosses a milestone for the first time in a cycle, the reward is granted. If the same milestone is crossed again later in the same cycle (e.g. after a reversal), the reward is granted again.
## Event reversal behavior
When a source event is reversed:
1. All `GAIN` and `MILESTONE` history events from that source are marked `reversed_at`.
2. An `EVENT_REVERSAL` history event is appended with the negative score delta.
3. If the reversed event was in the **active cycle**, `current_score`, `current_streak`, `period_score`, and `period_event_count` are decremented.
4. The `highest_milestone_score` is recalculated from surviving milestone events.
5. If the score drops below a previously reached milestone, re-triggering the qualifying event will grant the milestone reward again.
Reversal is idempotent: reversing the same event twice has no additional effect.
## Manual admin reset
Support operators can force-reset a customer's active streak cycle without waiting for the loss cron or a scheduled reset. This appends a `MANUAL_RESET` history event, starts a new `current_cycle_id`, and zeros score counters for the active cycle.
**Endpoint (internal):** `POST /api/admin/score-streaks/reset`
| Field | Description |
| - | - |
| `account_id` | Account UUID |
| `campaign_id` | Score streak campaign UUID |
| `customer_id` | Customer external id |
| `reason` | Audit reason stored in history (for example `Support requested reset`) |
| `actor_id` | Optional operator id for the audit trail |
Requires the `checksum` header with the `CRON_AUTH_SECRET` value. This endpoint is not part of the public Storefront API.
## Comparison with punch cards
| | Score Streaks | Punch Cards |
| - | - | - |
| Trigger | Any event type | Any event type |
| Progress unit | Score (unlimited) | Punches (capped) |
| Completion | Milestones (repeating) | Single completion |
| Time pressure | Loss window + optional reset | None |
| Event history | Full JSON log | None |
| Reversal | Score rollback | Standard wallet |
| Cron required | Yes (expire lost streaks) | No |
# Smart Archiver
Source: https://docs.masivo.ai/concepts/smart-archiver
Operational overview of Masivo's custom-event archiving pipeline.
Smart Archiver moves aged **custom events** from the `events` table into compressed Parquet files in object storage, then optionally deletes the database rows. It keeps query performance stable for high-volume accounts without losing historical data.
Smart Archiver is an **internal operational feature**. It is not exposed on the
public Storefront API. This page documents behavior for operators and support
engineers.
## What gets archived
| Scope | Detail |
| - | - |
| Rows | `events` where `type` is a custom event type for the account |
| Retention | **30 calendar days** (`SCA_RETENTION_DAYS`) before the archive cutoff |
| Storage bucket | `smart-archiver-events` |
| Format | Parquet shards + `manifest.json` per day |
| Path pattern | `{accountId}/{base64url(eventType)}/{year}/{month}/{day}/` |
A separate **historical archiver** cron handles other entities (`transactions`, `journey_steps`, and legacy `events` by `updated_at`) in the `archived-data` bucket with different retention windows.
## How archiving runs
```mermaid theme={null}
flowchart LR
CRON[Cron: archive_custom_events] --> LOCK[Per-account Redis lock]
LOCK --> DAY[Process one day per event type]
DAY --> FETCH[get_custom_events_for_archive_day]
FETCH --> PARQUET[Write Parquet shards]
PARQUET --> DEL[Delete DB rows]
DEL --> KV[Advance progress in KV]
```
**Cron endpoint:** `POST /api/cron/maintenance/archive_custom_events`
Protected by the `checksum` header with the `CRON_AUTH_SECRET` value.
Per run defaults:
* Up to **20,000** records globally (`SCA_RECORD_LIMIT_PER_RUN`)
* Concurrency across accounts, event types, and days
* Per-account lock (\~11 minutes TTL) to prevent overlapping runs
## Deferral rules
Archiving **skips** a day when:
* A pending first-run audience still depends on that custom event type
* An active audience **rehydration** job overlaps the same event type and date range
Rehydration reads Parquet files back into `events` so audiences can be rebuilt from archived data. Smart Archiver defers while rehydration is in progress.
## Progress tracking
Progress is stored in Redis/KV per account and event type:
```
archive:custom_events:{account_id}:current_date
```
Default start date for new accounts: **2024-06-01**.
Pass `force_reset_smart_archive_progress: true` in the cron body to reset all accounts to the default start date.
## Configuration flags
| Flag | Default | Effect |
| - | - | - |
| `SCA_SAVE_ONLY` | `false` | When `true`, writes Parquet but does not delete DB rows |
| `ARCHIVE_HARD_DELETE` | `true` | When `true`, removes rows after successful archive |
| `SCA_RETENTION_DAYS` | `30` | Days before events become eligible |
## Monitoring
The cron response includes operational counters such as `total_records_archived`, `deferred_accounts`, and `locked_accounts`. Use these to confirm steady progress and detect backlog.
## Related documentation
* [Data architecture](/resources/data-architecture) — how events flow through Masivo
* [Analytics exports](/guides/analytics-exports) — exporting reports that may reference archived periods
# Stores
Source: https://docs.masivo.ai/concepts/stores
Learn what stores are and how they can help you create a successful loyalty program.
Stores are the physical locations where you sell your goods and services to customers. You can synchronize your e-commerce stores with Masivo to create advanced filtering rules. For example, you can set up a campaign that rewards customers who make a purchase at a specific store.
# Teams
Source: https://docs.masivo.ai/concepts/teams
Organize account members and restrict analytics visibility with teams.
Teams are account-scoped groups that control **who sees which resources in the dashboard**. They are separate from account membership (users on an account) and from roles (what actions a user can perform).
## Teams vs members vs roles
| Concept | Purpose |
| - | - |
| **Members** | Users who belong to the account (`users_by_accounts`) |
| **Teams** | Organizational units for analytics scoping |
| **Roles** | Permissions (`read`, `write`, `delete`) per resource area |
A member can belong to multiple teams. A member with **no team assignments** (and who is not ADMIN or owner) only sees resources with empty `team_ids`.
## Entity team picker
When creating or editing a resource, use the **Teams** field to assign visibility:
* Select one or more teams to restrict analytics to those members.
* Choose **No teams** to leave `team_ids` empty so everyone can see the resource.
The picker lists only teams the current user belongs to (except ADMIN users, who see all teams).
## Dashboard navigation
| Section | Path | Actions |
| - | - | - |
| Members | `/settings/team` | Invite, assign roles and teams, remove members |
| Teams | `/settings/teams` | Create, edit, view team members |
| Roles | `/settings/roles` | Define custom permission sets |
| Invitations | `/settings/invitations` | Track pending invites |
## Permissions
| Action | Requirement |
| - | - |
| View members, teams, invitations | Account member |
| Invite members | `settings:write` |
| Create / edit / delete teams | `settings:write` / `settings:delete` |
| Assign member teams | `settings:write` |
| Remove members | `settings:delete` |
The built-in **ADMIN** role has all permissions. Custom roles define per-resource access across 30+ areas (campaigns, rewards, marketing automation, and more).
## Invitations
Member invitations expire after **24 hours**. Recipients accept via `/auth/accept-invitation?id=…` after signing in with the invited email.
## Related documentation
* [Manage teams](/guides/manage-teams) — step-by-step dashboard guide
# Tickets & communication
Source: https://docs.masivo.ai/concepts/tickets
How tickets, stages, assignees, and multi-channel threads fit together in Masivo.
## What is a ticket?
A **ticket** is a work item your team tracks from first contact until it is resolved or closed. Think of it as a case file: it has a title, a priority, an owner, a **pipeline**, a current **stage** on the board, and links to related records (reviews, customers, journeys, campaigns, or events).
Tickets live in the **CRM → Tickets** area of the dashboard. You can also create and update them through the Storefront API if you integrate Masivo with your own tools (help desk, inbox connector, custom portal, etc.).
## The three layers you need to understand
Masivo separates **case management** from **conversation storage**. This keeps the ticket board fast while still storing full message history.
| Layer | What it is | Where you see it |
| - | - | - |
| **Ticket** | The case: stage, priority, assignees, resolution fields | Ticket board, ticket detail page |
| **Communication thread** | One conversation channel on a ticket (email thread, WhatsApp chat, etc.) | Conversation tab, thread list API |
| **Communication message** | A single inbound/outbound/internal message inside a thread | Timeline, messages API |
```mermaid theme={null}
flowchart TB
subgraph ticket [Ticket]
T[Title, stage, priority, assignees]
end
subgraph threads [Communication threads]
E[Email thread]
W[WhatsApp thread]
R[Review thread]
end
subgraph messages [Messages per thread]
M1[Inbound customer email]
M2[Agent reply]
M3[Internal note]
end
ticket --> E
ticket --> W
ticket --> R
E --> M1
E --> M2
E --> M3
```
**Important:** listing threads returns **thread shells** (summary fields only). To read the full conversation, call the **messages** endpoint for each thread.
## Ticket lifecycle (pipelines, stages, and status)
Every ticket belongs to one **pipeline** and one stage inside that pipeline. Create separate pipelines only when support processes have genuinely different stages; teams that share a process should share a pipeline.
Each stage has a stable internal `code`, a display name, an order, and an internal `stage_type`:
| `stage_type` | Effect |
| - | - |
| `open` | The ticket remains active. Moving from a completed stage records a reopen. |
| `resolved` | The ticket is resolved and records `resolved_at`. |
| `closed` | The ticket is closed and records `closed_at`. |
```mermaid theme={null}
stateDiagram-v2
[*] --> OpenStage: pipeline default stage
OpenStage --> OpenStage: another open stage
OpenStage --> ResolvedStage: stage_type = resolved
OpenStage --> ClosedStage: stage_type = closed
ResolvedStage --> OpenStage: reopen
ClosedStage --> OpenStage: reopen
```
**Status** (`open`, `resolved`, `closed`) is derived from the stage's internal type—not its display name. You normally change **pipeline/stage**, not status directly. Each pipeline receives its own stable stage codes.
Some stages require extra text before the move is allowed:
| Target stage | Required field in `required_fields` | Purpose |
| - | - | - |
| `resolved` | `resolution_summary` | Short explanation of how the issue was fixed |
| `closed` | `closure_reason` | Why the ticket was closed |
Configure pipelines under **CRM → Pipelines**. Every active pipeline must keep exactly one open default stage, at least one open stage, and at least one closed stage. A used stage cannot be removed or change its internal state until its tickets are moved.
When an inbound customer message arrives on a closed ticket, Masivo reopens the same ticket in the **first open stage of its current pipeline**. It does not create a replacement ticket.
## Category hierarchy
Classification describes the problem independently from its workflow stage.
Configure it under **CRM → Categories** as one hierarchy. A top-level category
groups related categories and defines their e-commerce availability. A leaf can
end at the second or third level; it is selectable on tickets and owns the
schema that agents complete.
```mermaid theme={null}
flowchart LR
A[Orders] --> B[Brand, channel, and store availability]
A --> C[Payments]
C --> D[Card declined]
D --> E[Ticket Schema]
B --> F[Allowed ticket e-commerce values]
```
| Configuration | Behavior |
| - | - |
| Category name | Names any level in the hierarchy |
| Parent category | Empty only for a top-level category; hierarchies support three levels |
| Brands | Availability inherited from the top-level category; empty means all |
| Channels | Availability inherited from the top-level category; empty means all |
| Stores | Availability inherited from the top-level category; empty means all |
| Schema type | `text`, `textarea`, or `select` |
| Placeholder | Optional hint displayed before the agent enters a value |
| Required | Prevents the dashboard form from saving an empty value |
| Options | Allowed values for a `select` Schema field |
When an agent creates or edits a ticket, category selectors appear progressively,
for example **Orders › Payments › Card declined**. Only a leaf can be selected.
Selecting it displays its Schema and restricts Brand, Channel, and Store to the
availability inherited from the top-level category. The database validates the
same restrictions.
All levels live in `ticket_categories`. A top-level category has a null
`parent_id`; every other category references its parent. The leaf Schema is
stored in `ticket_categories.schema`. Tickets reference the selected leaf through
`category_id` and store validated values in `category_answers`. A category in
use cannot be removed until its tickets are moved.
## Ticket sources
| `source_type` | Meaning |
| - | - |
| `manual` | Created by a user or API call |
| `review` | Created from a customer review |
| `journey` | Created by a marketing automation journey |
| `system` | Created automatically by Masivo |
Review-sourced tickets automatically get a **review communication thread** with the review answers as the first inbound message.
## Assignees
Each ticket can have team members in two roles:
| Role | Meaning |
| - | - |
| `assignee` | Owns the ticket and is the only role that works it |
| `watcher` | Follows the ticket read-only |
Being linked to a ticket **only** as a watcher makes it read-only for that person: they cannot change its stage, edit it, comment, reply, reassign, or delete it, even when their account role allows writing tickets. Admins keep their global bypass, and anyone with no link to the ticket still acts according to their role, so supervisors can pick up unassigned work.
Assignee changes emit journey events (`TICKET_ASSIGNEE_ADDED`, `TICKET_ASSIGNEE_REMOVED`) that you can use in [Ticket Journey Triggers](/guides/ticket-journey-triggers).
## Agents and automatic assignment
Everyone who owns a ticket is a **ticket agent**. Assigning a team member by hand adds them to the roster automatically, with automatic routing switched off so they only receive what you hand them.
Each agent declares how much work they can take and when:
| Setting | Meaning |
| - | - |
| `availability` | `available`, `busy` or `offline`, changed by the agent |
| `max_open_tickets` | Capacity; routing skips the agent once this many stay open |
| `auto_assign` | Whether the agent is in the automatic rotation |
| `pipeline_ids` | Pipelines the agent takes work from; empty means all |
| `category_ids` | Categories the agent can resolve; empty means all |
| `working_days`, `workday_start`, `workday_end` | Shift, evaluated in the account timezone |
A ticket created without an assignee goes to the **available agent with the lightest open load**, breaking ties by who was assigned least recently. The agent must be on shift, under capacity, subscribed to the ticket's pipeline, and skilled in its category or in one of that category's parents. Accounts with no roster keep creating unassigned tickets.
Skipping routing for a single ticket is possible by creating it with `auto_assign: false`; the decision is stored on the ticket, so later sweeps respect it too.
### When every agent is busy
If nobody can take the ticket at creation time it stays unassigned instead of failing. An externally scheduled job (`POST /api/cron/tickets/auto_assign`) sweeps the pending backlog. The first sweep after an agent closes work, comes back from a break, or starts their shift assigns the waiting ticket. The sweep interleaves pipeline/category routes so an unsupported category cannot block unrelated work; within each route, urgent and older tickets go first.
Agents can also be pulled in manually from the ticket detail with **Auto-assign**, which runs the same routing on demand.
### Agent profiles and performance
Open **CRM → Agents** to inspect an agent's current load, shift, pipeline and category routing, recent ticket flow, average resolution time, reopen rate, and open work by priority. The summary compares the latest 30-day window with the previous one.
Lifetime metrics keep tickets the agent previously held after reassignment and count each ticket once. The ticket tabs show the agent's current active assignments, split into open, resolved, and closed work.
## Communication threads
A **thread** represents one external conversation identity, for example:
* One email mailbox thread (`channel: email`, `external_thread_id` from your provider)
* One WhatsApp number conversation (`channel: whatsapp`)
* One web chat session (`channel: chat`)
* The review submission tied to a ticket (`channel: review`, created automatically)
### Thread fields that matter day to day
| Field | What it tells you |
| - | - |
| `latest_message_preview` | Short text shown on the board card |
| `latest_message_at` | When the last message arrived |
| `latest_message_direction` | `inbound`, `outbound`, `internal`, or `system` |
| `unread_count` | Inbound messages not marked read (increments on inbound without `read_at`) |
| `external_thread_id` | Your provider's thread id — use for idempotent sync |
| `channel_account_id` | Mailbox, phone number, or inbox identifier |
### Thread uniqueness (avoid duplicates)
If you sync from an external system, always send **`external_thread_id`** (and usually **`channel_account_id`**) when creating threads and messages. Masivo deduplicates on:
* Thread: `(account, channel, channel_account_id, external_thread_id)`
* Message: `(account, channel, external_message_id)`
Sending the same external ids again returns the existing record instead of creating duplicates.
## Communication messages
Each message has:
| Concept | Values | Plain English |
| - | - | - |
| `direction` | `inbound`, `outbound`, `internal`, `system` | Who the message is "from" relative to your team |
| `message_type` | `message`, `reply`, `internal_note`, `system_event`, `review_event` | Shape of the message |
| `sender_type` | `customer`, `agent`, `email`, `system`, … | Who sent it |
| `parent_message_id` | UUID or null | If set, this message is a **reply** to that message **in the same thread** |
**Rules to remember:**
* Replies must reference a `parent_message_id` that exists **in the same thread**. Cross-thread replies are rejected.
* At least one of `subject`, `body_text`, or `body_html` is required when creating a message.
* **Internal notes** (`direction: internal`, `message_type: internal_note`) are visible to agents only. They do **not** emit customer reply journey events.
## Ticket `communication_summary`
When you fetch a single ticket (`GET /tickets/{id}`), the response includes **`communication_summary`**: a rollup across all threads on that ticket (thread count, latest preview, total unread, dominant channel). Use this for list views and board cards without loading every message.
## Events emitted (for journeys and webhooks)
Ticket and communication actions publish internal behavior events. Common ones:
| Event | When it fires |
| - | - |
| `TICKET_CREATED` | New ticket |
| `TICKET_STAGE_CHANGED` | Stage move |
| `TICKET_ASSIGNEE_ADDED` / `TICKET_ASSIGNEE_REMOVED` | Assignee changes |
| `TICKET_RESOLVED` / `TICKET_CLOSED` / `TICKET_REOPENED` | Stage transitions with those outcomes |
| `COMMUNICATION_THREAD_CREATED` | New thread |
| `TICKET_THREAD_ATTACHED` | Thread linked to a ticket |
| `COMMUNICATION_MESSAGE_CREATED` | Any new message |
| `TICKET_MESSAGE_RECEIVED` | Inbound message on a ticket thread |
| `TICKET_CUSTOMER_REPLIED` | Inbound message on a ticket thread |
| `TICKET_AGENT_REPLIED` | Outbound message on a ticket thread |
See [Ticket Journey Triggers](/guides/ticket-journey-triggers) to automate follow-ups when these events occur.
## Dashboard vs API
| Task | Dashboard | API |
| - | - | - |
| Discover pipelines and internal stage codes | CRM → Pipelines | `GET /ticket-pipelines` |
| Configure and assign dynamic categories | CRM → Categories and the selected category | Dashboard only |
| Move ticket on board | Drag card or change pipeline/stage in detail view | `PATCH /tickets/{id}` with `to_pipeline_id` and `to_stage_code` |
| Read conversation | Conversation tab on ticket detail | `GET /communication/threads?ticket_id=…` then `GET …/messages` |
| Sync inbound email from provider | — | `POST /communication/threads` then `POST …/messages` |
| Assign teammate | Ticket detail sidebar | `PATCH /tickets/{id}` or assignee endpoints |
For step-by-step API recipes and copy-paste examples, continue with the [Ticket management guide](/guides/ticket-management).
## Journey compensation
Ticket journeys can issue a reward to the ticket's current customer using
**Compensate customer**. Tickets without a customer and tickets blocked by a
compensation policy continue normally. Each ticket can be compensated once;
customer limits can apply across the account, within a journey, or for a reward.
Cooldowns, rolling caps, lifetime caps and an existing-reward check prevent
repeated awards when a customer opens more tickets. Successful grants remain in
the compensation history after redemption or expiration. Customer journeys can
use the same action without a ticket and share these customer limits with ticket
journeys.
See [Ticket Journey Triggers](/guides/ticket-journey-triggers#compensate-a-customer)
for configuration, defaults, concurrency and failure behavior.
# Tiers
Source: https://docs.masivo.ai/concepts/tiers
Learn what tiers are and how they can help you create a successful loyalty program.
Tiers are a way to make your customers feel special. In Masivo you can give your customers different benefits based on their tier. This can be a great way to encourage customers to engage with your brand. For example you can offer a discount to customers who have reached a certain tier.
Learn how to create your own system in our [Creating a tier system guide](/guides/setup-a-tier-system).
## Tier scopes
Campaigns and rewards can have a global tier scope:
* A campaign tier scope controls which customers can earn its rewards.
* A reward tier scope controls which customers can redeem that reward.
* No selected tiers means that customers in any tier, including customers
without a tier, are eligible.
Masivo reads the customer's current tier from their profile. Integrations do not
send or override the tier during an event or redemption. For an event that also
changes the customer tier, campaign eligibility uses the tier the customer had
when event processing started.
The global scope is different from the singular **Customer tier** condition. If
both are configured, both must match. For example, a scope containing Gold and
Platinum plus a condition requiring Gold only allows Gold customers.
# Transactions
Source: https://docs.masivo.ai/concepts/transactions
Learn what transactions are in Masivo.
Transactions are records of rewards accumulated or redeemed by your customers. They provide a detailed history of reward transfers, including the originating event, the campaign that offered the reward, and the receiver and sender of the reward.
Transactions are created automatically by Masivo when a reward is **ACCUMULATED**, **REDEEMED**, **TRANSFERRED**, or **EXPIRED**.
# Wallets
Source: https://docs.masivo.ai/concepts/wallets
Learn what wallets are in Masivo.
Wallets are where your customers' rewards are stored, with each reward tracked separately as a wallet line. Each wallet line has its own expiration date, and the wallet shows the sum of all wallet lines of the same type.
One important consideration is that only non-expired wallet lines are included in the total. For example, if you have 10 points that expire in 30 days and 5 points that expire in 60 days, the total will be 15 points initially. After 30 days, the total will be 5 points.
# Customers
Source: https://docs.masivo.ai/guides/CDP/customers
Understanding how customers and contacts work together in Masivo's Customer Data Platform
# Customers & Contacts
In Masivo's Customer Data Platform, **Customers** and **Contacts** are the building blocks that create a complete picture of your customer relationships.
## What are Customers?
A **Customer** in Masivo represents a unique individual or business entity that interacts with your brand. It's your "single source of truth" for each person or company you serve.
### Customer Profile Contains:
* **Personal Information**: Name, email, phone number, address
* **Behavioral Data**: Purchase history, website visits, app usage
* **Engagement History**: Email opens, campaign interactions, support tickets
* **Preferences**: Communication preferences, product interests
* **Lifecycle Stage**: New, active, at-risk, churned, or loyal customer
## What are Contacts?
**Contacts** are the individual touchpoints that belong to a Customer. Each contact represents a specific way you've connected with someone.
### Contact Examples:
* **Email Contact**: Newsletter subscription
* **Website Contact**: Website visit with email provided
* **Mobile App Contact**: App download and usage
* **Purchase Contact**: In-store or online purchase
## How They Work Together
**Step 1: Contact Collection**
* Customer visits website → Creates website contact
* Subscribes to newsletter → Creates email contact
* Makes purchase → Creates purchase contact
**Step 2: Identity Resolution**
* Masivo recognizes these contacts belong to the same person
* Matches them using email addresses or other identifiers
* Links all contacts to create one unified customer profile
**Step 3: Complete Customer View**
* You see the full customer journey across all touchpoints
* You can provide personalized experiences based on their entire history
## Related architecture
* [Registration flow](/resources/data-architecture#registration-flow) — How signup creates the customer profile and wallet
* [Activity & events](/resources/data-architecture#activity-events) — How behavioral data enriches the customer record
* [Marketing & destinations](/resources/data-architecture#marketing-destinations) — How audience membership and automation use customer data
* [Privacy & consent](/resources/data-architecture#privacy-consent) — How consent controls profile use and deletion
## Next Steps
* [Set up Identity Resolution](/guides/CDP/unify/overview) - Configure how contacts get unified into customers
* [Learn about Events](/guides/CDP/events) - See how customer actions are captured
* [Explore Integrations](/guides/CDP/integrations) - Connect your existing tools
***
**Ready to unify your customer data?** Start building complete customer profiles that drive better experiences.
# Events
Source: https://docs.masivo.ai/guides/CDP/events
Understanding how events work in Masivo's Customer Data Platform
# Events
Events are the actions that your customers take that can trigger automated campaigns and track their behavior. Think of events as the building blocks that help you understand what your customers are doing and when they're doing it.
## What are Events?
An **Event** in Masivo represents any action performed by a customer that you want to track or use to trigger automated responses. Events help you understand customer behavior and create personalized experiences.
### Event Examples:
* **PURCHASE**: When a customer makes a purchase
* **REGISTRATION**: When someone signs up for your platform
* **BIRTHDAY**: When it's a customer's birthday
* **TIER\_ADJUSTMENT**: When a customer's tier level changes
* **ABANDONED\_CART**: When a customer leaves items in their cart (coming soon)
Each event captures important moments in your customer's journey with your brand.
## How Events Work
### Event Collection
Events are automatically collected when customers interact with your brand:
* **Website actions**: Page views, form submissions, button clicks
* **Mobile app interactions**: App opens, feature usage, in-app purchases
* **Email engagement**: Opens, clicks, unsubscribes
* **Purchase activities**: Orders, returns, refunds
* **Support interactions**: Ticket creation, chat sessions
### Event Processing
When an event is received, Masivo:
1. **Validates the event** to ensure it's properly formatted
2. **Links it to the customer** using their unique identifier
3. **Checks for matching campaigns** that should be triggered
4. **Processes rewards** if the event qualifies for any campaigns
5. **Updates customer profiles** with the new behavior data
## Event Types in Masivo
### Default Events
These are the most common events that businesses track:
**PURCHASE** (Recommended)
* Tracks when customers make purchases
* Includes order details, products, and value
* Triggers reward accumulation campaigns
**REGISTRATION**
* Captures new customer sign-ups
* Often triggers welcome campaigns
* Helps track customer acquisition
**BIRTHDAY**
* Tracks customer birthdays
* Perfect for birthday campaigns and special offers
* Creates opportunities for personalized outreach
**TIER\_ADJUSTMENT**
* Monitors when customers move between loyalty tiers
* Triggers tier-specific campaigns
* Helps track customer progression
**ABANDONED\_CART** (Coming Soon)
* Will track when customers leave items in their cart
* Enables cart abandonment recovery campaigns
* Helps recover lost sales
### Custom Events
You can also create custom events for specific business needs:
* **Product views** for recommendation campaigns
* **Video completions** for content engagement
* **Survey submissions** for feedback collection
* **App feature usage** for product adoption
* **Event attendance** for offline engagement
Define the expected payload shape in the dashboard under **Events → Custom event types** (Fields schema). See [Custom event types](/guides/custom-event-types) for steps and an example schema.
## How Events Connect to Campaigns
### Behavior-Based Campaigns
Events are the foundation of behavior-based campaigns:
* **Purchase events** trigger reward accumulation
* **Registration events** start welcome sequences
* **Birthday events** activate special offers
* **Tier changes** trigger tier-specific communications
### Journey Triggers
Events can also trigger customer journeys:
* **Welcome Journey**: Starts with registration event
* **Purchase Journey**: Begins with first purchase event
* **Re-engagement Journey**: Triggered by inactivity events
* **Upsell Journey**: Activated by specific purchase patterns
### Real-Time Responses
Events enable immediate customer responses:
* **Instant rewards** for qualifying purchases
* **Immediate confirmations** for important actions
* **Real-time notifications** for engagement
* **Automatic follow-ups** based on behavior
## Why Events Matter for Your Business
### Complete Customer Understanding
Events help you see the full picture of customer behavior:
* **What customers do** across all touchpoints
* **When they do it** and how frequently
* **Why they do it** based on context and timing
* **What happens next** in their journey
### Personalized Experiences
Events enable you to create relevant experiences:
* **Right message** at the right time
* **Appropriate rewards** for specific actions
* **Targeted campaigns** based on behavior patterns
* **Proactive support** when customers need help
### Better Business Decisions
Events provide valuable insights:
* **Customer behavior patterns** across your business
* **Most valuable customer actions** and touchpoints
* **Campaign effectiveness** and engagement rates
* **Opportunities for improvement** and optimization
## Related architecture
* [Activity & events](/resources/data-architecture#activity-events) — Loyalty vs tracking-only paths and what each event updates
* [Marketing & destinations](/resources/data-architecture#marketing-destinations) — How events trigger journeys and destination sync
## Next Steps
Now that you understand how events work, you're ready to:
* [Set up Identity Resolution](/guides/CDP/unify/overview) - Connect events to unified customer profiles
* [Learn the onboarding process](/guides/CDP/unify/onboarding) - Get started with event tracking
* [Explore use cases](/guides/CDP/unify/use-cases) - See practical examples of event-driven campaigns
***
**Ready to track customer behavior?** Start capturing events that drive better customer experiences and business growth.
# Overview
Source: https://docs.masivo.ai/guides/CDP/integrations
Connect third-party tools to sync data from Masivo's Customer Data Platform
# Integrations
Integrations allow you to connect Masivo's Customer Data Platform with your favorite third-party tools, creating a seamless flow of customer data across your entire marketing and analytics stack.
## What are Integrations?
**Integrations** in Masivo are connections that automatically sync your customer data, events, and campaigns with external platforms. Think of them as bridges that keep all your tools updated with the latest customer information.
### What Gets Synced:
* **Customers**: Creation, updates, deletions, and customer tags
* **Events**: Behavioral events generated by your applications
* **Purchases**: Order and product information
* **Audiences**: Customer membership reflected in platform-specific audience APIs
Some platforms expose **separate destinations** for events vs audiences (Braze,
TikTok, Meta, Google Ads). Attach the Events destination for behavior tracking
and the Audiences destination when syncing segments.
## How Integrations Work
### Automatic Data Flow
Once configured, integrations work automatically:
1. **Enable Integration**: Set up the connection in Settings → Integrations
2. **Automatic Sync**: Masivo automatically syncs data when:
* Customers are created, updated, or deleted
* Events occur in your applications
* Purchases are fulfilled
* Audiences are recalculated
3. **Efficient Processing**: Data is batched and sent to your chosen platform optimally
### Smart Validation
Before sending data, Masivo validates:
* Your configuration is correct
* Required features are enabled
* Data meets your business rules
* Events are appropriate for the target platform
### Efficient Processing
* Data is batched automatically to respect platform limits
* Failed syncs don't affect other integrations
* Real-time processing ensures timely data delivery
### Integration Categories
Masivo supports various types of integrations:
**Email Marketing**
* Customer engagement platforms
* Newsletter and campaign tools
* Marketing automation systems
**Website & Mobile**
* Analytics platforms
* User behavior tracking
* App performance monitoring
**Server & Cloud Sources**
* Data warehouses
* Cloud storage systems
* ETL platforms
**Event Streams**
* Real-time data processing
* Event-driven architectures
* Stream analytics
## Setting Up Integrations
### Step 1: Choose Your Integration
1. Go to **Settings → Integrations** in your Masivo dashboard
2. Browse available integrations by category
3. Select the platform you want to connect
### Step 2: Configure Connection
1. **API Key**: Enter your platform's API key
2. **Endpoint**: Configure the correct REST endpoint
3. **Permissions**: Ensure proper permissions for the features you want to use
4. **Features**: Enable the specific sync options you need
### Step 3: Test and Activate
1. **Test Connection**: Verify the integration works correctly
2. **Configure Sync Options**: Choose what data to sync
3. **Activate**: Enable the integration to start syncing
## Common Use Cases
### Marketing Automation
**Scenario**: Sync customer data with your email marketing platform
* **Customer Updates**: New signups automatically appear in your email lists
* **Behavioral Triggers**: Customer actions trigger email campaigns
* **Audience Sync**: Segmented audiences stay updated across platforms
### Analytics & Reporting
**Scenario**: Send customer events to your analytics platform
* **Purchase Tracking**: Order data flows to your analytics dashboard
* **Behavioral Analysis**: Customer actions are tracked across all touchpoints
* **Custom Events**: Business-specific events are captured and analyzed
### Customer Support
**Scenario**: Keep your support platform updated with customer information
* **Customer Profiles**: Support agents see complete customer history
* **Purchase Context**: Support tickets include relevant order information
* **Customer Status**: Tier changes and preferences sync automatically
## Related architecture
* [Registration flow](/resources/data-architecture#registration-flow) — How customer profiles are created and synced to destinations
* [Activity & events](/resources/data-architecture#activity-events) — How behavioral events and purchases reach external tools
* [Connected destinations](/resources/data-architecture#connected-destinations) — How audience and event data sync to ad platforms, CRM, and warehouse
* [Privacy & consent](/resources/data-architecture#privacy-consent) — How consent and deletion affect outbound sync
## Next Steps
Now that you understand how integrations work, you're ready to:
* [Set up Identity Resolution](/guides/CDP/unify/overview) - Connect integrations to unified customer profiles
* [Learn about Events](/guides/CDP/events) - Understand what data gets synced
* [Explore Customers & Contacts](/guides/CDP/customers) - See how customer data flows through integrations
***
**Ready to connect your tools?** Start syncing your customer data across all your favorite platforms for a unified view of your customers.
# Amazon S3
Source: https://docs.masivo.ai/guides/CDP/integrations/amazon-s3/overview
Learn how to integrate Amazon S3 with Masivo CDP for data export and analytics
Event exports preserve nested attribution; Parquet stores the object as a JSON string.
See the [shared attribution guide](/api-reference/guides/attribution-tracking).
## What is Amazon S3 Integration?
The Amazon S3 integration is a **data warehouse destination** that allows you to export customer data, events, and transactions from Masivo to your Amazon S3 bucket. This integration enables you to store and analyze your loyalty program data using AWS services and third-party analytics tools. It supports **historical data backfill**, making it ideal for comprehensive data warehousing and analytics use cases.
## Key Features
Warehouse destination that supports historical data export and backfill
Choose exactly which data types to export: customers, events, purchases,
transactions, rewards, wallets, tiers, and audiences
Export data in JSON or Parquet format for different use cases
Configure export frequency: daily, weekly, or monthly based on your needs
Automatically backfills historical data based on your selected frequency
Uses IAM roles for secure, programmatic access to your S3 bucket
## Integration Type
### Amazon S3 as Warehouse Destination
Amazon S3 is configured as a **warehouse destination** that exports data to your S3 bucket. You have full control over which data types are exported, and the integration supports historical data backfill based on your configured export frequency.
## Data Flow
Customer data, events, and transactions are collected in Masivo
Data is validated and transformed for S3 export
Data is batched and compressed for efficient S3 upload
Data is stored in organized folders within your S3 bucket
## Supported Data Types
You can selectively enable or disable each data type for export. The following data types are available:
* Customer profiles and attributes - User lifecycle events (create, update,
delete) - Customer tags (optional) - Profile information and metadata
* Customer behavior events - Engagement tracking - Custom event properties -
Event timestamps and metadata
* Purchase events and transaction data - Purchase details and metadata
* Detailed transaction records - Financial transaction data - Transaction
history
* Reward records and redemption history - Reward program data
* Wallet balance information - Wallet transaction history
* Tier assignments - Tier change events - Tier membership data
* Audience calculation events (when audiences are calculated) - Audience
deletion events (when audiences are deleted)
## File Organization
Data is automatically organized in your S3 bucket based on the data type and export frequency you configure:
Files organized by day: `{DATA_TYPE}/YYYY/MM/DD/` - Example:
`CUSTOMER/2024/01/15/`
Files organized by week: `{DATA_TYPE}/YYYY/MM/week-N/` - Example:
`EVENT/2024/01/week-2/`
Files organized by month: `{DATA_TYPE}/YYYY/MM/` - Example:
`TRANSACTION/2024/01/`
## Export Formats
Choose between two file formats based on your analytics needs:
* Human-readable format - Larger file sizes - Easy to process with any tool
* Default format - Best for: Quick data inspection, simple integrations
* Columnar storage format - Smaller file sizes (better compression) -
Optimized for analytics and big data processing - Best for: Data warehouses,
analytics platforms, cost optimization
## Export Frequencies
Configure how often data is exported to S3:
* Exports data every day - Files organized by day (YYYY/MM/DD) - Best for:
Daily reporting and analytics - Default frequency
* Exports data once per week - Files organized by week (YYYY/MM/week-N) -
Best for: Weekly reporting, batch processing
* Exports data once per month - Files organized by month (YYYY/MM) - Best
for: Monthly reporting, long-term storage optimization
The export frequency determines when historical data backfill occurs.
Historical data is automatically synced based on your selected frequency.
## Benefits
Complete data warehouse solution with historical backfill support
Export only the data types you need, reducing storage costs and processing
time
Use AWS analytics services like Athena, Redshift, or third-party tools
Choose your preferred format (JSON/Parquet) and frequency
(daily/weekly/monthly)
Secure backup of all customer and loyalty program data
Meet data retention and compliance requirements
## Next Step
Learn how to configure your S3 integration
# Braze
Source: https://docs.masivo.ai/guides/CDP/integrations/braze/overview
Learn how to integrate Braze with Masivo CDP for seamless customer data synchronization
## What is Braze Integration?
The Braze integration allows you to synchronize customer data, events, and audience information between Masivo and Braze in real-time. This integration enables you to leverage Braze's powerful customer engagement platform while maintaining your loyalty program data in Masivo.
## Key Features
Customer data, events, and purchases are synchronized in real-time between
Masivo and Braze
Masivo audiences are automatically synced as custom attributes in Braze user
profiles
Customer behavior events are tracked and sent to Braze for enhanced
segmentation
Transaction data including products, amounts, and currencies are
synchronized
## Integration Type
Masivo exposes **two separate Braze destinations** so each connection sends only
one kind of data:
| Destination | Catalog ID | What it sends |
| - | - | - |
| **Braze Events** | `braze-events-destination` | Events, purchases, customer tags, journey attributes |
| **Braze Audiences** | `braze-audiences-destination` | Audience membership as custom attributes |
### Braze Events destination
* **Customer Profiles**: User attributes, tags, and profile information
* **Events**: Customer behavior and engagement events
* **Purchases**: Transaction data with product details
### Braze Audiences destination
* **Audiences**: Custom attributes indicating audience membership (`masivo_{audience}`)
Older combined `braze-destination` connections are legacy. Create separate
Events and Audiences destinations for new setups.
## Data Flow
Customer data is collected in Masivo through various touchpoints
Data is validated and transformed according to Braze requirements
Processed data is sent to Braze via REST API
Braze uses the data for segmentation, campaigns, and analytics
## Supported Data Types
* External ID (customer identifier) - Email address - Device tokens for push
notifications - Custom attributes and tags - Profile information
* Customer behavior events - Engagement tracking - Custom event properties -
Timestamp information
* Product SKUs and details - Transaction amounts and currency - Order status
and fulfillment - Purchase timestamps
* Audience membership attributes - Dynamic audience updates - Audience
deletion handling
## Benefits
Use Masivo loyalty data to create more precise customer segments in Braze
Combine loyalty program insights with Braze engagement data
Trigger Braze campaigns based on loyalty program activities
Get comprehensive customer insights across both platforms
## Next Step
Learn how to configure your Braze integration
# Facebook Apps Events
Source: https://docs.masivo.ai/guides/CDP/integrations/facebook-apps-events/overview
Learn how to integrate Facebook Apps Events with Masivo CDP for mobile app event tracking and conversion optimization
UTM are sent in `custom_data.utm_*`, with captured `fbc`/`fbp` in `user_data`.
See the [shared attribution guide](/api-reference/guides/attribution-tracking).
## What is Facebook Apps Events Integration?
The Facebook Apps Events integration allows you to track customer events, purchases, and conversions from Masivo directly to Facebook's App Events API. This integration enables you to measure the effectiveness of your Facebook advertising campaigns for mobile apps and create custom audiences based on your loyalty program data.
## Key Features
Track customer events, purchases, and conversions from mobile apps to
Facebook App Events
Optimize Facebook ad campaigns based on actual customer behavior and
purchases from mobile apps
Create Facebook custom audiences based on mobile app loyalty program
activities
Track customer journey and attribution across Facebook ads and mobile app
engagement
## Integration Type
### Facebook Apps Events as Destination
When configured as a destination, Masivo sends customer events to Facebook App Events API:
* **Customer Events**: Registration, identification, and engagement events from mobile apps
* **Purchase Events**: Transaction data with product details and values
* **Custom Events**: Loyalty program specific events and behaviors
* **App Lifecycle Events**: Install, open, and session tracking
## Data Flow
Customer events and purchases are collected in Masivo through mobile app
touchpoints
Events are mapped to Facebook App Events standard events (Purchase,
CompleteRegistration, AppActivated)
Customer data is processed and PII is hashed for privacy compliance
Events are sent to Facebook App Events API for conversion tracking and
audience building
## Supported Event Types
* **CUSTOMER/USER/IDENTIFY** → CompleteRegistration - Customer registration
and profile creation events - User identification and authentication events
* **PURCHASE/TRANSACTION** → Purchase - Transaction data with product
details - Purchase amounts, currencies, and quantities
* **EVENT/TRACK** → Custom events - Customer behavior and engagement events
* Custom loyalty program activities
* **PAGE/PAGEVIEW** → AppActivated - App lifecycle and navigation events -
Customer journey tracking
## Data Mapping
* Email (hashed) - Phone (hashed) - External ID - Client IP - User Agent -
Facebook Click ID (fbc) - Facebook Browser ID (fbp) - Device information
(brand, model, OS)
* App information and metadata - Install referrer - URL schemes -
Subscription ID - Facebook Login ID
* Product IDs and names - Product categories and brands - Order IDs and
values - Currency and quantities - Search terms - Custom properties
## Privacy & Compliance
Email and phone numbers are automatically hashed before sending to Facebook
Supports Facebook's data processing options for GDPR compliance
Supports configurable tracking settings for privacy compliance
Respects customer opt-out preferences for data sharing
## Benefits
Track the complete customer journey from Facebook ads to mobile app
engagement
Optimize Facebook ad campaigns based on actual mobile app conversions
Create lookalike audiences based on your most valuable mobile app customers
Get comprehensive insights into mobile app customer behavior
## Next Step
Learn how to configure your Facebook Apps Events integration
# Facebook Audiences v2
Source: https://docs.masivo.ai/guides/CDP/integrations/facebook-audiences-v2/overview
Sync Masivo audiences to Meta Custom Audiences with automatic audience provisioning
## What is Facebook Audiences v2?
**Facebook Audiences v2** (`facebook-audiences-v2-destination`) syncs Masivo
audience membership into Meta Custom Audiences. One destination covers all
Masivo audiences: Meta Custom Audiences are created automatically per audience
using your ad account credentials.
Prefer v2 for all new setups. The legacy
[Facebook Audiences](/guides/CDP/integrations/facebook-audiences/overview)
destination (static `audience_id`) is deprecated.
## Key Features
Meta Custom Audiences are created per Masivo audience and stored on the
audience attributes
Full and diff syncs upload hashed emails for APPEND and DELETE
Configure `ad_account_id`, app credentials, and access token once
Email identifiers are hashed before upload to Meta
## Requirements
* Meta ad account ID (`ad_account_id`)
* App ID and App Secret
* Access token with `ads_management` and custom audiences permissions
## Catalog ID
`facebook-audiences-v2-destination`
## Related
* [Facebook Pixel](/guides/CDP/integrations/facebook-pixel/overview) for event
streaming
* [Facebook Apps Events](/guides/CDP/integrations/facebook-apps-events/overview)
for app events
* [Legacy Facebook Audiences](/guides/CDP/integrations/facebook-audiences/overview)
# Facebook Audiences
Source: https://docs.masivo.ai/guides/CDP/integrations/facebook-audiences/overview
Legacy Facebook Custom Audiences destination (deprecated — use v2)
This destination (`facebook-audiences-destination`) is **deprecated**. Use
[Facebook Audiences v2](/guides/CDP/integrations/facebook-audiences-v2/overview)
(`facebook-audiences-v2-destination`) for new connections. Existing v1
connections continue to process for compatibility.
## What is Facebook Audiences Integration?
The legacy Facebook Audiences integration syncs customer data from Masivo to a
**single** pre-created Facebook Custom Audience (you paste a static
`audience_id`). v2 replaces this model with ad-account credentials and
automatic Meta audience creation per Masivo audience.
## Key Features
Sync customer data to Facebook Custom Audiences for targeted advertising
Create audiences based on loyalty program tiers, engagement, and behavior
PII data is hashed and processed according to Facebook's privacy
requirements
## Integration Type
### Facebook Audiences as Destination
When configured as a destination, Masivo syncs customer data to Facebook Custom Audiences:
* **Customer Profiles**: User information, demographics, and contact details
* **Behavioral Data**: Customer engagement and loyalty program activities
* **Segmentation Data**: Customer tiers, tags, and custom attributes
* **Purchase History**: Transaction data and purchase patterns
## Data Flow
Customer data is collected in Masivo through loyalty program activities
Customer information is processed and PII is hashed for privacy compliance
Customer data is organized and prepared for Facebook Custom Audiences
Data is sent to Facebook Custom Audiences API for audience creation
## Supported Data Types
* Email addresses (hashed) - Phone numbers (hashed) - External customer IDs
* First and last names - Date of birth - Gender - Location data (city,
state, country, zip code)
* Customer engagement events - Loyalty program activities - Purchase history
* Custom attributes
* Customer tiers and levels - Custom tags and attributes - Audience
membership - Behavioral segments
## Audience Types
Upload customer email addresses and phone numbers for direct targeting
Target customers based on loyalty program engagement and activities
Create audiences based on purchase history and transaction patterns
Target customers based on loyalty program tiers and status
## Privacy & Data Protection
Email addresses and phone numbers are automatically hashed using Facebook's
required methods
Supports Facebook's data processing options and privacy controls
Only necessary customer data is sent to Facebook for audience creation
Respects customer privacy preferences and opt-out requests
## Benefits
Target your most valuable customers with highly relevant Facebook ads
Create lookalike audiences based on your best loyalty program customers
Gain insights into customer behavior across your loyalty program and
Facebook
## Audience Examples
Customers in top loyalty tiers with high purchase frequency
Customers who haven't engaged recently or are showing churn signals
Recently registered customers who haven't made their first purchase
Customers who have purchased specific product categories
## Integration Requirements
* Facebook App ID and App Secret - Access token with custom\_audiences
permission
* Custom Audience ID - Proper audience configuration in Facebook Business
Manager
* ads\_management permission - custom\_audiences permission - Business
Manager access
## Next Step
Learn how to configure your Facebook Audiences integration
# Facebook Pixel
Source: https://docs.masivo.ai/guides/CDP/integrations/facebook-pixel/overview
Learn how to integrate Facebook Pixel with Masivo CDP for enhanced conversion tracking and customer insights
UTM are sent in `custom_data.utm_*`, with captured `fbc`/`fbp` in `user_data`.
See the [shared attribution guide](/api-reference/guides/attribution-tracking).
## What is Facebook Pixel Integration?
The Facebook Pixel integration allows you to track customer events, purchases, and conversions from Masivo directly to Facebook Pixel. This integration enables you to measure the effectiveness of your Facebook advertising campaigns and create custom audiences based on your loyalty program data.
## Key Features
Track customer events, purchases, and conversions in real-time to Facebook
Pixel
Optimize Facebook ad campaigns based on actual customer behavior and
purchases
Create Facebook custom audiences based on loyalty program activities
Track customer journey and attribution across Facebook and your loyalty
program
## Integration Type
### Facebook Pixel as Destination
When configured as a destination, Masivo sends customer events to Facebook Pixel:
* **Customer Events**: Registration, identification, and engagement events
* **Purchase Events**: Transaction data with product details and values
* **Custom Events**: Loyalty program specific events and behaviors
* **Page Views**: Customer interaction tracking
## Data Flow
Customer events and purchases are collected in Masivo through various
touchpoints
Events are mapped to Facebook Pixel standard events (Purchase,
CompleteRegistration, ViewContent)
Customer data is processed and PII is hashed for privacy compliance
Events are sent to Facebook Pixel for conversion tracking and audience
building
## Supported Event Types
* **CUSTOMER/USER/IDENTIFY** → CompleteRegistration - Customer registration
and profile creation events - User identification and authentication events
* **PURCHASE/TRANSACTION** → Purchase - Transaction data with product
details - Purchase amounts, currencies, and quantities
* **EVENT** → source `data.type` name - Customer behavior and custom events -
Custom loyalty program activities
* **PAGE/PAGEVIEW** → ViewContent - Page view tracking and navigation events
* Customer journey tracking
## Data Mapping
* Email (hashed) - Phone (hashed) - External ID - Client IP - User Agent -
Facebook Click ID (fbc) - Facebook Browser ID (fbp)
* Product IDs and names - Product categories and brands - Order IDs and
values - Currency and quantities - Search terms - Custom properties
## Privacy & Compliance
Email and phone numbers are automatically hashed before sending to Facebook
Supports Facebook's data processing options for GDPR compliance
Optional test event codes for development and testing environments
Respects customer opt-out preferences for data sharing
## Benefits
Track the complete customer journey from Facebook ads to loyalty program
engagement
Optimize Facebook ad campaigns based on actual loyalty program conversions
Create lookalike audiences based on your most valuable loyalty program
customers
## Use Cases
Track loyalty program sign-ups and purchases from Facebook traffic
Retarget customers who engaged with your loyalty program but didn't convert
Build custom audiences based on loyalty program tier and engagement
Test different Facebook ad creatives and audiences using loyalty program
data
## Next Step
Learn how to configure your Facebook Pixel integration
# Google Ads Remarketing
Source: https://docs.masivo.ai/guides/CDP/integrations/google-ads-remarketing/overview
Sync Masivo audiences to Google Ads customer match lists via the Data Manager API
## What is Google Ads Remarketing?
The Google Ads Remarketing destination syncs customers from Masivo audiences into a
Google Ads **Customer Match** user list. You can then use that list for remarketing
and lookalike-style campaigns in Google Ads.
This integration is separate from [Google Ads conversion
upload](/guides/CDP/integrations/google-ads/overview). Remarketing syncs **audience
membership**; conversion upload sends **purchase and event conversions**.
Remarketing uses the **Google Data Manager API** with OAuth and a GCP project.
It does **not** require a Google Ads **developer token**.
## Key Features
Add and remove customers when Masivo audiences change
Populate a Google Ads user list for remarketing campaigns
Email, phone, and name are hashed with SHA-256 before upload
Optional Login customer ID for Manager (MCC) / client account setups
## Integration type
### Destination (audience sync)
When enabled as a destination on a Masivo audience:
* **Add**: Customers entering the audience are uploaded to your Google Ads user list
* **Remove**: Customers leaving the audience (or delete signals) are removed by hashed email
* **Background processing**: A cron worker drains the `google-ads-remarketing` queue in batches
## Data flow
```mermaid theme={null}
flowchart LR
A[Audience change in Masivo] --> B[Queue message]
B --> C[Remarketing cron]
C --> D[Hash PII]
D --> E[Data Manager API]
E --> F[Google Ads user list]
```
A Masivo audience is created, recalculated, or a member is added/removed.
Destination jobs are enqueued on the `google-ads-remarketing` queue.
The cron batches members, hashes identifiers, and calls Data Manager
`audienceMembers:ingest` or `audienceMembers:remove`.
Members appear in your Customer Match user list for campaign targeting.
## Identifiers sent
| Field | Treatment | When used |
| - | - | - |
| Email | Trim, lowercase, SHA-256 (HEX) | Add and remove |
| Phone | Digits only, SHA-256 (HEX) | Add (if present) |
| First / last name | Trim, lowercase, SHA-256 (HEX) | Add (with full address) |
| Country code | Plain | Add (with full address) |
| Postal code | Plain | Add (with full address) |
At least one of **email** or **phone** is required for an add. Removals require
**email** only.
## Requirements
* Active Google Ads account - Customer Match user list (Audience Manager) -
Numeric Customer ID and User List ID
* GCP project with **Data Manager API** enabled - OAuth 2.0 Web client
(Client ID + Client Secret) - Refresh token with scope
`https://www.googleapis.com/auth/datamanager`
* Google Ads API developer token - Google Ads API enablement for this
destination (Data Manager is used instead)
## Comparison with Google Ads conversions
| | Remarketing | Conversions |
| - | - | - |
| Purpose | Sync audience lists | Upload conversion events |
| API | Data Manager | Google Ads API |
| Developer token | Not used | Required |
| Main IDs | User List ID, GCP Project ID | Conversion Action ID |
| Catalog ID | `google-ads-remarketing` | `google-ads-conversions` |
## Next step
Configure credentials, OAuth scope, and verify the connection
Track purchases and events as Google Ads conversions
# Google Ads
Source: https://docs.masivo.ai/guides/CDP/integrations/google-ads/overview
Learn how to integrate Google Ads with Masivo CDP for conversion tracking and campaign optimization
## What is Google Ads Integration?
The **Google Ads Conversions** destination (`google-ads-conversions`) uploads
customer conversion events and purchases from Masivo to Google Ads via the
Google Ads Conversion Upload API. Use it to measure campaign effectiveness and
optimize on loyalty and purchase outcomes.
Conversion upload requires a Google Ads **developer token**, OAuth credentials,
and a Conversion Action ID. For syncing **audiences** to Customer Match lists
(no developer token; Data Manager API + GCP project), use
[Google Ads Remarketing](/guides/CDP/integrations/google-ads-remarketing/overview).
## Key Features
Track customer conversions, purchases, and events in real-time to Google Ads
Optimize Google Ads campaigns based on actual customer behavior and
purchases
Match uploaded conversions using captured gclid, gbraid or wbraid
Track customer journey and attribution across Google Ads and your loyalty
program
## Integration Type
### Google Ads as Destination
When configured as a destination, Masivo sends customer conversions to Google Ads:
* **Conversion Events**: Customer purchases, registrations, and key actions
* **Transaction Data**: Purchase amounts, currencies, and product details
* **Customer Data**: Captured click identifiers for better matching
* **Custom Events**: Loyalty program specific conversions
## Data Flow
Customer conversions and purchases are collected in Masivo through various
touchpoints
Events are mapped to Google Ads conversion actions and enhanced conversions
Customer data is processed and formatted for Google Ads API requirements
Conversions are sent to Google Ads via Conversion Upload API for campaign
optimization
## Supported Conversion Types
* Transaction data with product details - Purchase amounts, currencies, and
quantities - Order IDs and transaction timestamps - Captured click identifiers
for matching
* Customer registration events - Account creation tracking - Enhanced
conversion data
* Loyalty program specific conversions - Custom event tracking - Key action
conversions
* Email addresses (hashed) - Phone numbers (hashed) - Customer addresses -
First and last names
## Data Mapping
* Email (hashed) - Phone (hashed) - First and last names - Address
information - External customer ID
* Order ID - Transaction value and currency - Product IDs and names -
Conversion timestamp
* Google Click ID (gclid) - Conversion environment - Conversion date and
time - Conversion value
## Privacy & Compliance
Email and phone numbers are automatically hashed before sending to Google
Ads
Supports Google's data processing options for GDPR compliance
Uses hashed customer data for improved conversion matching while maintaining
privacy
Respects customer opt-out preferences for data sharing
## Benefits
Track the complete customer journey from Google Ads to loyalty program
conversions
Optimize Google Ads campaigns based on actual loyalty program conversions
Improve conversion tracking accuracy with enhanced conversion data
Measure the true ROI of your Google Ads campaigns with accurate conversion
data
## Related: audience remarketing
For syncing Masivo **audiences** into Google Ads Customer Match lists (no
developer token; Data Manager API), see [Google Ads
Remarketing](/guides/CDP/integrations/google-ads-remarketing/overview).
## Next Step
Learn how to configure your Google Ads conversion integration
Sync audiences to Google Ads Customer Match lists
# Google Analytics
Source: https://docs.masivo.ai/guides/CDP/integrations/google-analytics/overview
Send events and purchases to the web and app streams in GA4
Masivo sends events and purchases through Google Analytics 4 Measurement
Protocol. Standard event names are lowercased, Masivo orders are mapped to
GA4 ecommerce fields, and custom events retain their flattened parameters.
Attribution requires the real Google tag or Firebase identity and session.
UTM parameters preserved by Masivo links must also reach the website/app's
analytics implementation. Masivo's own click tracking does not create GA4
sessions.
Configure web, Android and iOS streams in the existing destination. The
optional event `ga4` context selects the appropriate stream. Events without a
real identity keep the legacy customer-ID fallback on the web stream;
this path does not provide reliable session attribution.
See the [setup guide](/guides/CDP/integrations/google-analytics/setup) and
[complete integration reference](/integrations/google-analytics) for link
rules, purchase mapping, client context and verification steps.
# TikTok
Source: https://docs.masivo.ai/guides/CDP/integrations/tiktok/overview
Learn how to integrate TikTok Events API with Masivo CDP for conversion tracking and ad optimization
## What is TikTok Integration?
The TikTok integration allows you to send customer events and conversions from Masivo to TikTok Events API in real-time. This server-side integration enables better ad optimization, audience targeting, and conversion tracking across your TikTok advertising campaigns, bypassing browser limitations and ad blockers.
## Key Features
Send events directly from your server, bypassing ad blockers and browser
restrictions
Automatically maps common e-commerce events to TikTok standard events
Works seamlessly with Masivo's native PURCHASE format with order objects
Track business-specific events beyond TikTok's standard catalog
## Integration Type
Masivo exposes **two separate TikTok destinations**:
| Destination | Catalog ID | What it sends |
| - | - | - |
| **TikTok Events** | `tiktok-events-destination` | Events API (`/event/track/`) — EVENT and PURCHASE |
| **TikTok Audiences** | `tiktok-audiences-destination` | Custom audiences via TikTok DMP / CSV upload |
### TikTok Events destination
When configured as a destination, Masivo sends conversion events to TikTok:
* **Standard Events**: ViewContent, AddToCart, InitiateCheckout, CompletePayment
* **Custom Events**: Business-specific events like ClickButton, Subscribe, Contact
* **Purchase Events**: Native Masivo PURCHASE format with order objects
### TikTok Audiences destination
* **Audiences**: Customer membership for custom audience targeting
Older combined `tiktok-destination` connections are legacy. Prefer separate
Events and Audiences destinations.
## Data Flow
Customer events and purchases are generated in Masivo
Events are validated against your TikTok integration settings
Events are transformed to TikTok Events API format with automatic field
mapping
Events are sent to TikTok Events API in real-time for immediate tracking
## Supported Event Types
Events that Masivo automatically converts to TikTok standard names: -
`purchase` / `PURCHASE` → `CompletePayment` - `add_to_cart` → `AddToCart` -
`begin_checkout` → `InitiateCheckout` - `add_payment_info` →
`AddPaymentInfo` - `view_item` → `ViewContent` - `search` → `Search` -
`sign_up` → `CompleteRegistration` - `add_to_wishlist` → `AddToWishlist`
Use these exact event names for TikTok-specific tracking: - `ClickButton` -
User clicks a button - `Contact` - User initiates contact - `Download` -
User downloads content - `SubmitForm` - User submits a form - `Subscribe` -
User subscribes to service - `PlaceAnOrder` - User places an order
Any event name not in TikTok's catalog becomes a custom event: -
`free_trial_started` - `newsletter_signup` - `video_watched_50` - Custom
events have parameters flattened with `custom_` prefix; campaign UTM also
retain their original `properties.utm_*` names
## Event Transformation
Masivo's native PURCHASE format is automatically transformed for TikTok:
* `order.products` → `contents` array - `order.value` → `value` -
`order.purchase_id` → `transaction_id` - `order.currency` → `currency`
* `sku` → `content_id` - `name` → `content_name` - `amount` → `quantity` -
`value` → `price`
## Benefits
Capture 100% of conversions with server-side tracking, not affected by ad
blockers
TikTok uses conversion data to optimize ad delivery and bidding
More control over data sharing with server-to-server integration
Bypass browser restrictions and cookie limitations for complete data
## TikTok Pixel vs Events API
No reliance on browser cookies or client-side tracking scripts
Capture 100% of conversions, not just those visible to browser pixels
Server-to-server communication bypasses all ad blockers
Use both Pixel (client-side) and Events API (server-side) for best results
* TikTok automatically deduplicates
## Use Cases
Track complete purchase funnel from product view to payment completion
Optimize campaigns for form submissions and registration completions
Track in-app purchases and subscription events for mobile apps
Track business-specific conversions unique to your industry
## Next Step
Learn how to configure your TikTok integration
Complete API documentation and event examples
# Overview
Source: https://docs.masivo.ai/guides/CDP/overview
Transform your customer data into a unified, actionable view with Masivo's Customer Data Platform
# Customer Data Platform (CDP)
Imagine having a complete, real-time view of every customer across all your touchpoints - website, mobile app, email campaigns, in-store visits, and customer support interactions. That's the power of Masivo's Customer Data Platform (CDP).
Masivo's CDP is designed specifically for businesses that want to understand their customers better and create more meaningful relationships through data-driven insights and personalized experiences.
## What is a Customer Data Platform?
A Customer Data Platform is your central hub for customer data. It collects, unifies, and organizes customer information from all your different systems and channels, creating a single, comprehensive profile for each customer.
Think of it as your customer intelligence center - instead of having scattered pieces of information about your customers across different tools, you get one complete picture that tells the whole story.
## Why Your Business Needs a CDP
### The Problem: Scattered Customer Data
Most businesses today face the same challenge:
* **Marketing team** has email engagement data
* **Sales team** has CRM and deal information
* **Support team** has ticket and interaction history
* **E-commerce platform** has purchase and browsing data
* **Mobile app** has usage and preference data
Each team sees only part of the customer story, leading to:
* Disconnected customer experiences
* Missed opportunities for personalization
* Inefficient marketing spend
* Frustrated customers receiving irrelevant communications
### The Solution: Unified Customer Intelligence
Masivo's CDP solves this by creating a single source of truth for all customer data:
* **Complete customer profiles** with data from all touchpoints
* **Real-time updates** as customers interact with your brand
* **Unified customer journey** tracking across all channels
* **Actionable insights** for better decision making
## How Masivo's CDP Works
Masivo's CDP operates through four key components that work together to create a complete customer view:
### 1. Data Collection
Your CDP automatically gathers customer data from all your connected systems:
* Website and mobile app interactions
* Email marketing platforms
* CRM and sales tools
* Customer support systems
* E-commerce platforms
* Social media channels
### 2. Data Unification
Using advanced Identity Resolution, Masivo connects all data points that belong to the same customer:
* Links anonymous website visits to known customer profiles
* Connects mobile app usage with email engagement
* Unifies purchase history across different channels
* Matches support interactions with customer profiles
### 3. Profile Creation
Creates rich, comprehensive customer profiles that include:
* Contact information and preferences
* Complete purchase and interaction history
* Behavioral patterns and engagement levels
* Customer journey stages and lifecycle status
* Preferences and segmentation data
### 4. Real-Time Activation
Makes this unified data available across your entire business:
* Personalized marketing campaigns
* Informed sales conversations
* Context-aware customer support
* Product recommendations
* Targeted promotions and offers
## Key Components of Masivo's CDP
### Customer & Contact Management
Masivo automatically creates and maintains detailed customer profiles by collecting individual touchpoints (contacts) from every interaction. This includes website visits, email subscriptions, mobile app usage, and purchase activities.
### Event Tracking
The platform captures and processes customer events in real-time, from purchases and registrations to birthday celebrations and tier changes. These events power personalized campaigns and customer journeys.
### Integration Hub
Masivo seamlessly connects with your existing tools and platforms, automatically syncing customer data, events, and campaigns with your marketing automation, CRM, analytics, and other business systems.
### Identity Resolution (Unification)
The core engine that intelligently matches and unifies customer data across all touchpoints, ensuring you have one complete view of each customer regardless of how they interact with your brand.
## Key Benefits for Your Business
### Enhanced Customer Experience
* **Personalized interactions** based on complete customer history
* **Consistent messaging** across all channels and touchpoints
* **Relevant recommendations** based on actual behavior and preferences
* **Seamless experiences** as customers move between channels
### Improved Marketing Effectiveness
* **Better targeting** with complete customer profiles
* **Higher conversion rates** through personalization
* **Reduced marketing waste** by avoiding irrelevant messaging
* **Improved customer lifetime value** through better engagement
### Operational Efficiency
* **Faster customer service** with complete interaction history
* **More informed sales conversations** with full customer context
* **Better inventory planning** based on unified purchase data
* **Streamlined operations** with single source of customer truth
### Data-Driven Decision Making
* **Complete customer journey visibility** for better strategy
* **Accurate customer analytics** based on unified data
* **Better segmentation** for targeted campaigns
* **Measurable ROI** on customer experience investments
## CDP Best Practices
### Start Simple, Scale Smart
* Begin with your most important data sources
* Focus on high-value customer touchpoints first
* Gradually add more data sources as you see results
* Continuously refine and improve your setup
### Focus on Data Quality
* Ensure consistent data formatting across sources
* Regularly audit and clean your customer data
* Set up validation rules for incoming data
* Monitor data completeness and accuracy
### Prioritize Privacy and Compliance
* Implement proper consent management
* Ensure GDPR and CCPA compliance
* Provide clear opt-out mechanisms
* Maintain transparent data usage policies
### Measure and Optimize
* Track key performance indicators
* Monitor customer experience improvements
* Measure business impact and ROI
* Continuously optimize based on results
## Ready to Transform Your Customer Data?
Masivo's Customer Data Platform turns scattered customer information into a powerful business asset. Start building unified customer profiles that drive better experiences, stronger relationships, and measurable business growth.
## Related architecture
* [Registration flow](/resources/data-architecture#registration-flow) — Where new customer profiles enter Masivo
* [Activity & events](/resources/data-architecture#activity-events) — How customer actions are captured and processed
* [Marketing & destinations](/resources/data-architecture#marketing-destinations) — How unified data powers segmentation and external sync
* [Privacy & consent](/resources/data-architecture#privacy-consent) — How consent gates CDP data use and sharing
### Next Steps
* [Learn about Customers & Contacts](/guides/CDP/customers) - Understand how customer data is structured
* [Explore Event Tracking](/guides/CDP/events) - See how customer actions are captured
* [Set up Integrations](/guides/CDP/integrations) - Connect your existing tools
* [Configure Identity Resolution](/guides/CDP/unify/overview) - Unify your customer data
***
**Ready to see your customers clearly?** Your unified customer view is just a few clicks away.
# Smart Menu
Source: https://docs.masivo.ai/guides/CDP/smart-menu
Personalized product recommendations based on customer preferences and behavior
# Product Recommendations
Masivo's Smart Menu transforms your product catalog into a personalized shopping experience that adapts to each customer's unique preferences and behavior. Instead of showing the same products to everyone, your customers see exactly what they're most likely to love.
## How Smart Menu Works
Smart Menu analyzes each customer's complete profile to understand their preferences, purchase history, and browsing behavior. It then creates personalized product categories that feel like they were handpicked just for them.
The system learns from every interaction, continuously refining recommendations to show customers the products they're most likely to purchase. Whether they're browsing your website, using your mobile app, or shopping in-store, each customer sees a menu tailored specifically to their tastes and needs.
## Personalization Features
**Preference-Based Categories** automatically organize products based on what each customer has shown interest in. A fashion lover sees clothing categories, while a tech enthusiast sees electronics sections, all without any manual configuration.
**Best Sellers** highlights your most popular products, but filtered through each customer's preferences. They see the best sellers in categories they actually care about, not just your overall top performers.
**You Might Like** uses advanced algorithms to predict products customers haven't tried yet but are likely to love, based on their past behavior and similar customers' preferences.
**Trending Now** shows what's popular among customers with similar profiles, helping them discover new products that align with their interests.
## Customization Options
Smart Menu adapts to your business structure, allowing you to customize recommendations by **store location**, **sales channel**, or **brand**. A customer shopping at your downtown location might see different trending items than someone browsing your suburban store, even if they have similar preferences.
You can also adjust the recommendation engine to prioritize different factors. Some businesses want to focus on profit margins, while others prefer to highlight customer satisfaction ratings or inventory levels. The system learns your priorities and adjusts recommendations accordingly.
## Real-World Impact
Customers spend more time browsing when they see products they're actually interested in. Smart Menu increases engagement by showing relevant products first, reducing the time customers spend searching for what they want.
The personalization also drives higher conversion rates because customers are more likely to purchase products that match their preferences. When every product suggestion feels curated for them, customers trust your recommendations and make more confident purchasing decisions.
Smart Menu also helps you understand your customers better by revealing patterns in their preferences and behavior. You can see which product categories resonate with different customer segments, helping you make smarter inventory and marketing decisions.
## Next Steps
* [Learn about Customer Data](/guides/CDP/customers) - Understand how customer preferences are captured
* [Explore Event Tracking](/guides/CDP/events) - See how behavior data powers recommendations
* [Set up Integrations](/guides/CDP/integrations) - Connect Smart Menu with your existing systems
***
**Ready to personalize your product experience?** Start showing customers exactly what they want to see, every time they visit.
# Onboarding
Source: https://docs.masivo.ai/guides/CDP/unify/onboarding
Your first steps into unified customer identity
# Your Journey to Unified Customer Identity
Imagine Sarah, a customer who visits your website on her laptop, signs up for your newsletter on her phone, and makes a purchase using her tablet. Without Identity Resolution, these would appear as three different customers in your system. But with Masivo's Identity Resolution, you can connect these interactions to see Sarah's complete journey.
## Before You Begin
Let's make sure you're ready to start connecting your customer data:
1. **Access granted**: You have the right permissions to configure identity rules
2. **Data flowing**: Your customer data is already coming into Masivo from your website, app, or other sources
3. **Fields identified**: You know which customer information (like email or phone) you want to use for matching
## Finding Identity Resolution
Your unified customer view is just a few clicks away:
1. Open your **Customers** section in the Masivo dashboard
2. Look for **Identity Resolution** in the side menu
3. Click to enter your identity management center
## Your First Look at Identity Resolution
When you first arrive, you'll see something powerful yet simple: your **Default Identity Resolution** section. Think of this as your foundation - the basic rules that help Masivo recognize when different customer records actually belong to the same person.
### What You're Seeing
The default rule you see is already working behind the scenes across ALL your connected data sources. Every Masivo account comes with this rule pre-configured, and it automatically applies to every integration you have - your website, mobile app, CRM, email platform, and any other system sending customer data to Masivo.
**"Sources"** - This rule looks for email fields in your customer data and uses them to connect related records, no matter which system they came from.
### The Power of Universal Coverage
Here's what makes this special: you don't need to set up separate rules for each of your data sources. Whether Sarah signs up on your website, downloads your mobile app, or contacts your support team, the same identity matching logic works across all these touchpoints automatically.
### Why This Matters
Every time a customer interacts with your brand:
* Signs up with their email on your website
* Makes a purchase through your mobile app
* Contacts support via your CRM system
* Downloads content from your email campaigns
This single default rule works across ALL these systems simultaneously, ensuring all these actions are connected to the same customer profile, giving you a complete picture of their relationship with your brand.
### One Rule, All Sources
Think of it this way: instead of having to create separate matching rules for your website, mobile app, CRM, email platform, and other integrations, you have one powerful rule that covers everything. This means:
* **Instant setup**: Your identity resolution starts working the moment you connect any data source
* **Consistent matching**: The same logic applies whether data comes from your website or your mobile app
* **No gaps**: Every customer touchpoint is covered automatically
* **Simplified management**: One rule to understand and customize, not dozens
## Understanding Your Customer Data
Let's continue with Sarah's story. As her interactions grow with your brand, you might notice patterns in your data:
* Sometimes she uses "[Sarah.Johnson@email.com](mailto:Sarah.Johnson@email.com)" for business inquiries
* Other times "[sarah.johnson@email.com](mailto:sarah.johnson@email.com)" for personal purchases
* Her phone number appears in different formats: "+1-555-123-4567" vs "(555) 123-4567"
### Making Sense of the Variations
This is where Identity Resolution becomes your detective. The default rules are smart enough to recognize these variations as the same person, but sometimes you might want to customize how matching works.
## Customizing Your Identity Rules
### When You Might Want to Edit Rules
Think about editing your default rules when:
* You have customers who use multiple email addresses
* Your data comes in different formats from various sources
* You want to match on phone numbers or other identifiers
* You need more precise or more flexible matching
### How to Edit Your Rules
Ready to make your rules work better for your specific data? Here's how:
1. **Click "Edit"** on your default rule
2. **Add conditions** - Maybe you want to match on phone numbers too
3. **Choose your logic** - Should it be email OR phone (more flexible) or email AND phone (more precise)?
4. **Consider advanced options** - Need time limits or group size controls?
5. **Save your changes** - Your new rules start working immediately
### Advanced Options (When You Need More Control)
For businesses with specific matching needs, you can enable advanced settings:
**Smart Protections:**
* **Maximum Group Size**: Set a limit (like 5 or 10) to prevent too many profiles from accidentally merging together
* **Time Windows**: Only match customers who interacted recently (like "in the last 30 days")
**When to use these:**
* You have customers with common names (like "John Smith")
* Your business is seasonal or event-based
* You want extra precision to avoid mixing up different customers
* You're dealing with large volumes of customer data
**How to enable:**
1. **Check "Enable advanced settings"** when editing a rule
2. **Set your maximum group size** (start with 10, adjust based on your needs)
3. **Add time limits** if your business has seasonal patterns
4. **Test with a small group** before applying to all customers
### Real-World Example: The Busy Restaurant Chain
FoodieChain noticed they were accidentally merging different customers named "Maria Garcia" who used the same phone number format. Here's how they fixed it:
1. **Enabled advanced settings**
2. **Set maximum group size to 3** (prevents over-merging)
3. **Added temporal limit**: "occurred in the last 90 days" (seasonal menu changes)
4. **Required both email AND phone** to match (more precise)
Result: Clean customer data with no more accidental merges, while still connecting genuine repeat customers.
## Seeing Your Rules in Action
### What Good Rules Look Like
Your rules will show up in plain English, making them easy to understand:
* "Email field contains 'email'" (matches any record with an email)
* "Email field contains 'sarah' AND Phone field equals '+15551234567'" (matches Sarah specifically)
* "Email field contains 'gmail.com' OR Phone field equals '+15551234567'" (matches Gmail users or that specific phone)
### The Magic Happens Automatically
Once your rules are set up, they work behind the scenes. Every new customer interaction gets checked against your rules, and if it matches an existing customer, their profile gets updated with the new information.
## Growing Your Identity Strategy
### Start Simple, Get Sophisticated
The beauty of Identity Resolution is that you can start simple and grow more sophisticated over time:
1. **Week 1**: Use the default email matching (you're already here!)
2. **Week 2**: Add phone number matching for mobile customers
3. **Month 1**: Create more specific rules for different customer types
4. **Month 3**: Fine-tune your matching to reduce false positives
### Signs Your Rules Are Working
You'll know your Identity Resolution is successful when:
* Customer profiles become richer with more complete data
* You see fewer duplicate customers in your system
* Your marketing campaigns become more effective with better targeting
* Customer service has a complete view of each customer's history
## Your Next Steps
Now that you understand the foundation, you can explore:
1. [Advanced configuration options](/guides/unify/space-setup)
2. [Real-world use cases](/guides/unify/use-cases)
3. [Understanding different types of identifiers](/guides/unify/external-ids)
Remember: Identity Resolution is not just about technology - it's about understanding your customers better and creating more meaningful relationships with them.
# Overview
Source: https://docs.masivo.ai/guides/CDP/unify/overview
Unified customer identity management across multiple data sources
# Identity Resolution
Identity Resolution is a system that allows you to unify and resolve your customers' identities across multiple data sources and channels. This functionality uses customizable rules to match customer profiles based on different identifying fields.
## What is Identity Resolution?
Identity Resolution is the process of combining data records that refer to the same real entity (in this case, a customer) but may appear in different formats or data sources. In Masivo, this means being able to identify that the same customer is interacting with your brand across different channels, devices, or platforms.
## Main Components
### Default Identity Rules
Every Masivo account comes with a pre-configured default rule that automatically applies to all your data sources. This rule cannot be deleted and serves as your foundation for customer matching across every integration you have - whether it's your website, mobile app, CRM, or any other connected system.
### Global Coverage
Since the default rule applies to all your data sources automatically, you don't need to configure anything separately for each integration. Whether a customer interacts through your website, mobile app, email campaigns, or any other connected platform, the same identity matching rules will work across all of them.
## Available Field Types
The system allows you to match customers using different types of identifying fields:
* **Email**: Email address
* **Phone**: Phone number
* **User ID**: Unique user identifier
* **Device ID**: Device identifier
* **IP Address**: IP address
* **Name**: Customer name
* **Custom Fields**: Any other fields from your data sources
## Key Benefits
* **Unified customer view**: Combines data from multiple touchpoints
* **Enhanced personalization**: Better understanding of the customer journey
* **Duplicate reduction**: Prevents duplicate customer records
* **More accurate analytics**: Cleaner data for insights and reports
## How It Works
Every Masivo account starts with a default identity rule already working for you. This rule automatically scans all your connected data sources - your website, mobile app, email platform, CRM, and any other integrations - looking for matching customer information.
The system uses Masivo's Rules Engine to evaluate conditions and determine when two records belong to the same customer. Rules are executed with different operators like "contains", "equals", and more.
### Rule Logic Options
**Alternative Conditions (OR logic)**
Match customers if ANY of the conditions are met. For example:
* Email contains "@company.com" OR Phone equals "+1234567890"
**Compound Conditions (AND logic)**
Match customers only if ALL conditions are met. For example:
* Email contains "@company.com" AND First name equals "John"
## Advanced Control Options
When you need more sophisticated customer matching, Identity Resolution offers advanced controls:
* **Smart Group Limits**: Prevent too many customer profiles from being merged together (perfect for businesses with common names or emails)
* **Time-Based Matching**: Only connect customer interactions that happened recently (great for seasonal businesses or limited-time campaigns)
* **Complex Logic**: Combine multiple matching conditions to create precise rules that fit your specific business needs
## Getting Started
To set up Identity Resolution:
1. **Access the feature**: Navigate to Customers → Identity Resolution
2. **Review default rules**: Check the existing default matching rules
3. **Create custom rules**: Add source-specific rules if needed
4. **Enable advanced settings**: Add time limits or group size controls if needed
5. **Test your configuration**: Verify rules work as expected
6. **Monitor results**: Review how customers are being matched
## Next Steps
* [Onboarding Guide](/guides/unify/onboarding) - Get started with configuring your first identity resolution rules
* [Space Setup](/guides/unify/space-setup) - Learn about the configuration interface
* [Use Cases](/guides/unify/use-cases) - See examples of rule configurations
* [External IDs](/guides/unify/external-ids) - Understand identifier management concepts
## Related architecture
* [Registration flow](/resources/data-architecture#registration-flow) — How CDP contacts merge into the customer profile at signup and identification
# Accumulating rewards
Source: https://docs.masivo.ai/guides/accumulating-rewards
Learn how customers can accumulate rewards
## How customers can accumulate rewards
In Masivo campaigns are the distribution channel of rewards. When a customer action or event matches the campaigns' rules, the customer automatically receive the reward.
## Facts about accumulation
* Customers can receive rewards from **multiple campaigns at the same time** if they meet the rules of each campaign.
* Customers won't receive rewards if they are outside the campaign's global
tier scope, or, for coupon campaigns, do not meet a **Customer tier** rule
condition.
* Campaign tier eligibility uses the customer's tier when event processing
starts, before the same event can change that tier.
* A campaign can contain multiple rules and **each rule can reward the customer** with a different reward.
* If some rule contain multiple conditions, the customer must meet **all AND conditions** to receive the reward.
* If some rule contain multiple conditions, the customer must meet **at least one OR condition** to receive the reward.
* **AND & OR conditions can be combined** in the same rule. Therefore, the customer must meet all AND conditions and at least one OR condition to receive the reward.
* Each campaign type comes with its own mechanism to accumulate rewards. For example, **behavior campaigns** accumulate rewards based on customer events, while **coupons campaigns** accumulate rewards when a coupon code is redeemed.
## Test rewards accumulation
Currently, the only way to test reward accumulation is by using our [REST
API](/api-reference). In the future, we will provide a playground to test
reward accumulation.
## Next steps
Want to analyze how customers are accumulating rewards?
* **View analytics**: Navigate to **Analytics > Rewards** to see accumulation patterns and trends
* **Export data**: Learn how to [export analytics tables to CSV](/guides/analytics-exports) for deeper analysis of reward distribution
## Related architecture
* [Activity & events](/resources/data-architecture#activity-events) — How qualifying events update wallet balances and tiers
# Catalogs
Source: https://docs.masivo.ai/guides/catalogs
Create catalogs from products and manage selections from the dashboard.
## Dashboard
1. Open **Marketing automation → Catalogs**.
2. Use **New catalog** to pick a brand and products; fields are derived from product data.
3. Open a catalog to view items and create selections (field, operator, value, limit).
## Storefront API
Authorize with your API key, then use `GET/POST /catalogs`, item routes under `/catalogs/{id}/items`, and selections under `/catalogs/{id}/selections`.
See the API reference for request schemas.
# Create templates
Source: https://docs.masivo.ai/guides/content-templates
Learn how to create template in Masivo
## Templates in Masivo
Templates in Masivo are available in **Campaigns, Rewards, and Tiers**. They ensure consistency, save time, and simplify content management.
### How Clients Use Templates
Clients integrate with Masivo to retrieve structured data from different entities For more information, please consult our [REST API](/api-reference). One of the key challenges when managing large amounts of content is ensuring that its structure remains consistent across multiple instances.
For example, a client may have **over 100 active campaigns** running simultaneously. Instead of manually defining the content structure for each campaign, templates allow them to **predefine a reusable format**, ensuring that all campaigns follow the same content structure.
Templates are beneficial because:
* They **standardize the structure of campaign content**, making it easier to manage at scale.
* Clients don’t need to **manually define the format** for each new campaign.
* A single template can be applied across multiple campaigns, ensuring consistency without repetitive effort.
### Where Templates Are Used
Templates apply specifically to the **content section** of campaigns, rewards, and tiers. When a client retrieves this data, it follows the predefined template, ensuring that all content maintains the same structure.
By using templates, clients can manage content efficiently without having to recreate formats for every new instance.
Now you understand how templates structure the content within campaigns,
rewards, and tiers, ensuring a reusable and consistent format across multiple
integrations.
## Creating a new template
To create a new template, navigate to the **Templates** section within the corresponding section (**Campaigns, Rewards, or Tiers**) in the dashboard.
Click the **New template** button to start creating a new template.
## Template Configuration
Each template consists of the following settings:
* **Name**: A descriptive name for the template. Helps distinguish templates in the list.
* **Description** *(Optional)*: Additional details about the template.
* **Status**: Active or inactive.
* **Schema Settings**: Define structured content by adding blocks.
## Adding Blocks to Templates
Blocks are used to enforce dynamic content structures within a template. When adding a block, the available settings depend on the selected type:
### Input Block
* **Input name**: The field identifier.
* **Input placeholder**: A hint or example value.
* **Required**: Whether the field must be filled out.
### Textarea Block
* **Textarea name**: The field identifier.
* **Textarea placeholder**: A hint or example value.
* **Required**: Whether the field must be filled out.
### Image Block
* **Image name**: The field identifier.
* **Required**: Whether the image is mandatory.
* **alt** *(Optional)*: Enforce the use of an alternative description (alt) in the image.
* **href** *(Optional)*: Enforce the use of a link (href) in the image.
After configuring your blocks, click **Save** to finalize the template.
## Using Templates
Once created, templates can be used across different sections of Masivo:
* **Campaigns**
* **Rewards**
* **Tiers**
A template created in **Campaigns** cannot be used in **Rewards** or **Tiers**
To use a template, simply select it from the **Content** section when creating a new entity and customize the values as needed.
That's it! You have created a template that can be applied within its
corresponding section in Masivo.
# Create and manage service forms
Source: https://docs.masivo.ai/guides/create-and-manage-review-forms
Learn how to create service forms, configure fields, automate tickets, and review submissions in Masivo
## Service forms in Masivo
Navigate to **Service forms** in CRM. The section has two tabs:
* **Service forms** — list and manage form definitions
* **Reviews** — all reviews across forms
The Storefront API keeps `/review-forms` in its paths for compatibility.
## Creating a new service form
On the **Service forms** tab, click **New form** (requires **review\_forms** write permission). The editor contains **Base Settings**, **Ticket automation**, and **Fields**.
### Base settings
Configure:
* **Name** — unique per account (used internally and in lists)
* **Description** — optional internal notes
* **Type** — `Rating`, `Comment`, `Thread`, or `Custom`
* **Status** — `Active` or `Inactive` (only active forms are available via the Storefront API)
### Fields
Use **Add field** to define each question or input that channels must send when creating a review.
For each field set:
* **Field name** — API key used in `answers` (for example `score`)
* **Label** — display label for your UI
* **Type** — Score, Text, Select, or Multi select
* **Required** — whether the answer is mandatory
Additional settings by type:
* **Score** — min and max values (default 1–5)
* **Select / Multi select** — use **Add option** for each choice (label and value)
* **Images** — optional, up to five per field; use **Add image**, then **Select an image** to pick or upload from your multimedia library, and set **Alt** text
Field names must be unique within the form and should stay stable after
launch. Clients submit answers keyed by field name, not by label.
### Ticket automation
Enable **Create tickets from responses** when submissions should open support work. Choose the pipeline and priority explicitly. Generated tickets enter the normal agent rotation and keep the review linked as their source.
**Open a ticket** decides which responses deserve one:
* **Every response** — one ticket per submission.
* **When conditions match** — use **Add conditions** to open the dedicated condition editor. Save there to return to the service form, where every condition group has edit and delete actions plus a readable summary. Conditions inside a group are combined with **AND**; separate groups are combined with **OR**.
Each condition picks a type in the modal:
* **Response answer** — evaluates one answer of the form (for example *Rating is at most 2*). Pick the answer from the **Answer** select; it lists the fields defined in this form, and the operators adapt to the field: comparisons for scores, text matching for open answers, and *equal to* / *not equal to* for selects. This condition only appears once the form has fields, so define them first.
* **Response sentiment score** — the AI sentiment score of the response, from 0 to 100.
* **Response emotion** — an emotion the AI detected, such as *Angry* or *Disappointed*.
* **Response risk flag** — a risk the AI detected, such as *Churn risk* or *Delivery issue*.
Answers are evaluated the moment the response arrives; conditions that read the AI analysis wait until the response has been analyzed, so those tickets show up a few minutes later.
Ticket creation happens after the review is saved. A legacy form that references a missing pipeline cannot create the ticket, but it does not reject the review; the failure is logged for operators. An unavailable roster creates the ticket unassigned for the queue sweep.
When you are done, click **Save** in the form actions panel to create the service form.
## Editing and deleting
Return to **Service forms** in CRM. In the table, each row shows the form name and actions:
* **View** — open reviews for that form only
* **Edit** — update settings and fields (requires **review\_forms** write)
* **Delete** — soft-delete the form (requires **review\_forms** delete)
The edit URL follows `/crm/review-forms/{reviewFormId}/update`.
## Reviewing submissions
### All reviews
Open **Reviews** in the secondary navigation (path `/crm/review-forms/reviews`) to see every review for the account. Use table filters and search to narrow results.
### Per service form
From the **Service forms** tab, click **View** on a row, or go to `/crm/review-forms/{reviewFormId}`. You see only reviews for that form.
The reviews table includes:
* **Received** — submission timestamp
* **Customer** — `customer_id` or `-` for anonymous
* **Source**, **Platform**, **Brand**, **Channel** — attribution dimensions
* **Answers** — JSON payload keyed by field names
## Next steps
To load service form definitions and submit reviews from your app, follow [Submit reviews](/api-reference/guides/submit-reviews) in the API reference.
For domain concepts (field types, events, permissions), see [Service forms](/concepts/review-forms).
# Create audiences
Source: https://docs.masivo.ai/guides/create-audiences
Learn how to create and manage dynamic customer audiences in Masivo
Audiences are automatically recalculated to ensure your customer segments stay
current and accurate. You can configure how frequently audiences are
recalculated based on your business needs
## What you'll learn
By the end of this guide, you'll know how to create dynamic customer segments that automatically update based on customer behavior and attributes, enabling you to deliver targeted marketing campaigns and personalized experiences.
## Step 1: Access the audiences section
Navigate to **Marketing Automation > Audiences** in your dashboard. This is your control center for creating and managing customer segments that power your automated marketing campaigns.
Click the **New audience** button to start building your first dynamic customer segment.
## Step 2: Configure your audience foundation
Start by establishing the core settings that will define how your audience operates:
**Choose a descriptive name**: Use specific names like "High-Value Customers" or "Cart Abandoners - 24h" instead of generic ones. This makes audiences easy to identify when building journeys and campaigns.
**Add a helpful description**: Write a brief explanation of the audience's purpose. For example: "Customers who have spent over \$500 in the last 6 months" or "Users who registered but haven't made their first purchase."
**Set brand scope**: Decide whether this audience applies to:
* **All brands**: Choose this for universal segments like "VIP Customers" that span your entire business
* **Specific brand**: Select this when creating brand-specific segments for targeted campaigns
Start with brand-specific audiences for your first segments. They're easier to
manage and create more targeted marketing experiences.
## Step 3: Configure audience frequency
Set how often your audience should recalculate to stay current with customer behavior:
**Choose your update frequency**:
* **Never**: For static, one-time segments that don't need updates
* **Daily**: For audiences based on recent activity (recommended for most use cases)
* **Weekly**: For segments based on weekly patterns or less time-sensitive criteria
* **Monthly**: For audiences based on monthly spending or long-term behavior
* **Quarterly/Yearly**: For segments based on seasonal patterns or annual metrics
More frequent updates provide better accuracy but consume more resources.
Choose the frequency that matches your business needs and campaign timing.
## Step 4: Define audience conditions
This is where you define who belongs in your audience using powerful condition builders
## What you've accomplished
You've created a dynamic customer audience that automatically maintains
accurate segments based on real-time customer behavior. Your audience will now
continuously update to ensure your marketing campaigns always target the right
customers at the right time.
## Related architecture
* [Audiences](/resources/data-architecture#audiences) — How membership is built, recalculated, and kept in sync
* [Activity & events](/resources/data-architecture#activity-events) — Where profile traits used in conditions come from
* [Marketing & destinations](/resources/data-architecture#marketing-destinations) — How audiences drive journeys and destination sync
# In-app templates
Source: https://docs.masivo.ai/guides/create-in-app-templates
Learn how to create and manage in-app message templates in Masivo
## What you'll learn
By the end of this guide, you'll know how to design in-app message templates, set delivery priority, and prepare them for audience schedules or journey actions.
## Step 1: Access in-app templates
Navigate to **Marketing Automation → In-app templates** in the dashboard.
Click **New in-app template** to open the editor.
## Step 2: Choose a message type
Select the surface that best fits your campaign:
* **Modal** — centered dialog for promotions or announcements
* **Fullscreen** — high-impact takeovers
* **Slide up** — bottom sheet for secondary actions
* **Banner** — lightweight top or bottom alerts
## Step 3: Build the template content
Add a descriptive **name**, optional **description**, and **brand** when you
manage multiple brands.
Enter a **title** and **body**. Add an optional **image URL** for a hero
visual and configure **buttons** (primary action, dismiss, deeplink, or
external URL).
Under **Delivery**, set **priority** when multiple messages are eligible for
the same customer. Higher values win.
Use the team picker to restrict who sees this template in analytics. Leave
empty for account-wide visibility.
Use the iOS and Android previews to validate layout before saving.
## Step 4: Test and schedule
**Send test** delivers the template to a test audience so you can verify rendering on real devices.
**Schedule** materializes messages for a selected audience at a chosen time. Large audiences are subject to the in-app send limit shown in the scheduler.
## A/B testing with variants
Use the **A/B testing** section to add up to four versions of the same
template (layout, copy, or design). Scheduled sends can split the audience by
percentage. Journey in-app actions pick one variant.
See [Template variants](/guides/template-variants) for limits, distribution,
tests, and analytics.
## Step 5: Use in journeys
Add an **In-app** action node in a journey and select your template. Connect the node to an event edge so messages materialize when customers hit that step.
For real-time delivery, pair journey in-app nodes with qualifying customer events. Your app should call `GET /inapp/capabilities` to learn which `eventTypes` trigger a refresh.
## Analytics
Open **Analytics → In-app** to review delivery, impressions, clicks, and dismissals.
## Next steps
* [Integrate in-app messages](/guides/in-app-messages) — connect your mobile app
* [Template variants](/guides/template-variants) — A/B test layouts and copy
* [Integrate in-app messages](/api-reference/guides/integrate-in-app-messages) — API examples
* [In-app messages concept](/concepts/in-app-messages) — lifecycle and configuration reference
# Journeys
Source: https://docs.masivo.ai/guides/create-journeys
Learn how to create and manage automated customer journeys in Masivo
## What you'll learn
By the end of this guide, you'll know how to build sophisticated automated workflows that respond to customer behavior in real-time, creating personalized experiences that drive engagement and conversions.
## Step 1: Access the journeys section
Navigate to **Marketing Automation > Journeys** in your dashboard. This is your command center for creating automated customer experiences.
Click the **New journey** button to start building your first automation workflow.
## Step 2: Define your journey foundation
Start by establishing the core parameters that will guide your automation:
**Name your journey strategically**: Use descriptive names like "New Customer Onboarding" or "Cart Abandonment Recovery." This helps you quickly identify the purpose when managing multiple journeys.
**Choose your audience wisely**:
* **Specific audience**: Select this when you want to target customers with particular characteristics (VIP customers, recent purchasers, etc.)
* **All customers**: Choose this for universal experiences like welcome sequences or general announcements
Start with a specific audience for your first journey. It's easier to create
targeted, effective automation when you know exactly who you're reaching.
## Step 3: Build your automation workflow
Now you'll create the actual automation using the visual journey editor. Think of this as building a flowchart that responds to customer actions.
### Create your first journey step
Every journey starts with a trigger. Here's how to set it up:
**1. Choose your trigger event**: Select what customer action will start this automation
**2. Set your conditions**: Define who qualifies for this step
**3. Configure time delay**: Decide when the action should happen (configured in minutes):
* **Fires immediately**: For urgent communications like order confirmations
* **Custom wait time**: Add strategic delays (e.g., wait 60 minutes)
**4. Add actions**: A new step shows **No actions yet**. Click **Add action**
and choose what happens when the customer reaches this step:
* **Send email**: Send a personalized email using your templates
* **Send push notification**: Deliver a mobile notification. The FCM `data`
payload includes `customerId` and does **not** include `notificationId`
(unlike dashboard test sends)
* **Send in-app message**: Deliver an in-app message from a template
* **Send whatsapp message**: Send an approved WhatsApp template
* **Send news feed card**: Publish a News Feed card from a template
* **Loop**: Send the customer back to an earlier step
* **Send event to destination**: Forward an event to a connected destination
* **Send to Meta (Customer List Custom Audience)**: Available when a Meta
Audiences destination is connected
* **Send to Braze (Client Custom Attribute)**: Available when a Braze
destination is connected
Ticket journeys use a different action list. See
[Ticket journey triggers](/guides/ticket-journey-triggers).
## Step 4: Connect your templates
Messaging actions use templates you already created:
* **Email**: email template, then an **Email variant** when the template has
more than one version
* **Push**: push template, then a **Push variant** when needed
* **In-app**: in-app template, then an **In-app variant** when needed
* **WhatsApp**: an approved WhatsApp template, then a **WhatsApp variant**
when needed
* **News feed**: news feed card template, then a **News feed variant** when
needed
If you don't have templates yet, create them first in Email, Push, WhatsApp,
In-app, or News Feed templates. A journey action cannot be saved until the
template (and variant, when required) is selected.
## Multiple actions
Each journey step can run **more than one action**. After the first action
exists, use **Add action** again to attach another.
The step header is **Actions**. The canvas shows the first action in full and
`+N more` when additional actions exist. Click the actions list to open the
panel, then edit or delete any action.
When the customer reaches the step, Masivo runs the actions **in the order
you added them**. Each messaging action can use a different template and, when
the template has variants, a specific variant.
A step can stay with **No actions yet**. The customer can still progress when
the step's trigger and wait time are met.
## Template variants in journey actions
If the selected template has more than one [template variant](/guides/template-variants),
the action form shows **Select a variant**. You must pick one version before
**Save** is enabled. That action always sends the chosen variant — journeys do
not split traffic by percentage on the template.
This is separate from [split variants](#split-variants-ab-paths), which divide
customers into different **paths**.
## Split variants (A/B paths)
Use split variants when you want to divide customers into different **paths** by
percentage (for example, Variant A = 70%, Variant B = 30%). This does not
change the copy inside a template. To A/B test message content, use
[template variants](/guides/template-variants).
### Where you can create a split
* You can create only **one split per journey**
* The split can be inserted only on **first-level steps** (direct children of
`Start journey`)
* After a split exists, you can still add regular nodes, but you cannot insert
a second split
### How to add more variants
After creating the split, click the split node to open the split form and use
**Add variant**.
* Do not use the `+` menu to create additional variants
* Variant branches are managed from the split form
### Variant rules
* Minimum weight: `0`
* Maximum weight: `100`
* Maximum variants per split: `8`
* If variants exist, total weight must be exactly `100%`
A variant with `0%` is valid and works as a paused branch while you keep its
structure configured.
### How distribution works
Customers are assigned to one variant using the configured weights.
* Example: A = 70%, B = 30%
* If 10,000 customers reach the split, expected distribution is \~7,000 in A and
\~3,000 in B
Percentages are probabilistic, so short-term numbers may vary slightly.
## Push payload on journey sends
A journey **Send push notification** action does not reuse the dashboard
test payload. Journey `data` includes `customerId` and `accountId`; it
omits `notificationId`. Test sends from the push editor do the opposite.
```json theme={null}
{
"customerId": "user_abc123",
"accountId": "00000000-0000-4000-8000-000000000001",
"link": "https://links.example.com/l/XyZ123"
}
```
`link` is the node short URL when the journey already mapped one. Otherwise
it is the template destination URL. `redirect_*` keys follow [Push
settings](/guides/settings/push) when configured.
For test, scheduled audience, and manual API payloads, see [FCM data
payload](/api-reference/guides/integrate-push-notification#fcm-data-payload).
## Compensate a customer
Add **Compensate customer** to an action node to issue a reward directly to the
customer executing the journey. A ticket is not required. Configure the reward,
amount, validity, cooldown and customer limits in the action dialog.
Limits can count compensation from both customer and ticket journeys. Retries
deduplicate each action within a step; new entries follow the configured policy.
See [compensation settings](/guides/ticket-journey-triggers#compensate-a-customer)
for scope, limits, reward availability and failure behavior.
## What you've accomplished
You've built an automated customer journey that responds intelligently to
customer behavior, delivering personalized experiences at scale. Your journey
will now work 24/7 to engage customers at the perfect moments.
## Related architecture
* [Activity & events](/resources/data-architecture#activity-events) — How qualifying events enter the loyalty and journey pipeline
* [Marketing & destinations](/resources/data-architecture#marketing-destinations) — How journeys and messages fit into the marketing data flow
# Links
Source: https://docs.masivo.ai/guides/create-links
Learn how to create and manage links in Masivo marketing automation
Links are the backbone of marketing automation measurement in Masivo. They enable you to track and analyze every marketing flow, whether you're using standalone links or embedding them within push notifications, emails, or journey workflows.
## What you'll learn
By the end of this guide, you'll know how to create trackable short links that drive traffic and measure engagement.
For better brand recognition, consider setting up your custom domain before
creating links. You can configure this in
[Links settings](/guides/settings/links).
## Step 1: Access the links section
Navigate to **Marketing Automation > Links** in the dashboard. You'll find it in the left sidebar - this is your hub for creating and managing all trackable links.
Click the **New link** button to start creating your first trackable link.
## Step 2: Configure your link basics
Start by setting up the fundamental properties that define how your link will work:
**Create a memorable alias**: Choose a short, descriptive name like "summer-sale" or "app-download." This helps you identify the link's purpose at a glance and makes it more trustworthy for users.
**Set your destination URL**: Enter the complete URL where users should land, including `https://`. This could be a product page, landing page, or any web destination that supports your campaign goals.
**Choose the right link type** based on your use case:
* **Default**: Use for general marketing campaigns, email newsletters, and social media posts
* **Ads**: Select when creating links specifically for paid advertising campaigns
* **QR**: Choose when you need a scannable QR code for print materials or physical displays
## Step 3: Customize your short link
**Select your domain**: Choose between the custom domains you've configured in your account settings.
**Create your custom path**: This becomes the end of your short URL. For example, if you enter "holiday-deals", your final link will be `msvo.link/l/holiday-deals`.
You can generate a random path using the shuffle button if you prefer, or copy
the complete short URL using the copy button for immediate use.
## Step 4: Set up smart redirects (advanced)
For mobile app marketing, configure custom redirects to create seamless user experiences:
**Enable custom redirects**: Toggle this on when you want different behavior for iOS and Android users.
**Configure iOS redirects**: Set where iPhone users go (typically your App Store listing or a specific app screen).
**Configure Android redirects**: Define where Android users land (usually your Google Play Store page or app deep link).
### Recover the destination after an app install
Normally, a user who doesn't have your app yet just lands on the app store — the original destination (a promo, a specific screen) is lost once they install and open the app for the first time. Deferred deep linking closes that gap: when the app opens, it can ask Masivo "was this install started from a link?" and get routed to the right place automatically.
This works differently on each platform:
* **Android**: works automatically for every link, no setup needed. Masivo attaches the click to the Play Store redirect, and the app reads it back through the Play Install Referrer API on first open.
* **iOS**: requires opt-in per link, since it relies on the clipboard. In **Advanced Settings**, enable **Deferred deep linking via clipboard (iOS)**. Before sending users to the App Store, they'll see a **Get the app** button that copies the link to their clipboard. The first time the app opens, iOS shows a system prompt asking to allow pasting — accepting it lets the app recover the destination.
This feature requires your mobile app to integrate the Masivo SDK's deep
linking module. Share the [integration
guide](/api-reference/guides/integrate-deferred-deep-linking) with your
development team if you want to enable it for your app.
## Step 5: Add UTM tracking (optional)
Masivo includes a built-in UTM builder if you need it for campaign tracking.
Click the UTM settings button next to your destination URL to add campaign tracking parameters:
* **Source**: Identify where traffic comes from (e.g., "newsletter", "facebook", "google")
* **Medium**: Specify the marketing medium (e.g., "email", "social", "cpc")
* **Campaign**: Name your specific campaign (e.g., "summer-sale-2024")
* **Term**: Add keywords for paid search campaigns
* **Content**: Differentiate between ad variations or email versions
These parameters help you track which campaigns drive the most valuable traffic in your analytics tools.
## Step 6: Generate QR codes for offline marketing
When you select **QR** as your link type, Masivo automatically creates a scannable QR code perfect for:
* Print advertisements and brochures
* Product packaging and labels
* Event displays and signage
* Business cards and flyers
The QR code updates automatically whenever you change the destination URL, keeping your offline materials current.
## What you've accomplished
You've created trackable links that provide valuable insights into your
marketing performance while delivering seamless user experiences. Your links
are now ready to drive traffic and measure engagement across all your
campaigns.
## Next steps
Once your links are active, you can track their performance and export analytics data:
* **Configure global link behavior**: Review [Links settings](/guides/settings/links) for domains, redirects, and QR branding
* **View analytics**: Navigate to **Analytics > Links** to see clicks, conversions, and traffic sources
* **Export data**: Learn how to [export analytics tables to CSV](/guides/analytics-exports) for deeper analysis and reporting
## Advanced link management
### Configure global settings
Visit **Marketing Automation > Settings > Links** to set up:
Detailed guide: [Links settings](/guides/settings/links)
**Custom domains**: Add your own branded domains instead of using `msvo.link` for better brand recognition and trust.
**Default redirects**: Set fallback iOS and Android URLs that apply to all links unless you specify custom redirects.
**QR code branding**: Upload your logo to appear in the center of all generated QR codes for consistent brand presentation.
## Related architecture
* [Marketing & destinations](/resources/data-architecture#marketing-destinations) — How trackable links support journeys, audience sends, and measurement
# Push notifications
Source: https://docs.masivo.ai/guides/create-push
Learn how to create and manage push notification templates in Masivo
## What you'll learn
By the end of this guide, you'll know how to create compelling push notification templates that grab attention, drive engagement, and seamlessly integrate with your automated marketing workflows.
## SDK Integration Requirements
Push notifications will not work without proper SDK integration. Complete FCM
setup before creating templates to ensure functionality.
**Required setup**: Your app needs FCM configuration for both iOS and Android platforms. This includes:
* Firebase project configuration with proper certificates
* Platform-specific setup (APNs for iOS, FCM for Android)
* Permission handling for notification access
* Token management for device targeting
**Testing requirements**: Push notifications must be tested on physical devices with preview builds - they won't work with Expo development servers or emulators.
**Helpful resources**:
* [React Native Firebase Documentation](https://rnfirebase.io/messaging/usage) - Official implementation guide
* [Expo Firebase Setup](https://rnfirebase.io/#your-expo-project) - Required configuration for Expo projects
* [Push settings guide](/guides/settings/push) - Configure Firebase SDK and token duration in Masivo
## Step 1: Access the push templates section
Navigate to **Marketing Automation > Push templates** in your dashboard. This is where you'll create and manage all your reusable push notification designs.
Click the **New push template** button to start creating your first push notification template.
## Step 2: Set up your template foundation
Start by establishing the organizational details that will help you manage your templates effectively:
**Choose a descriptive name**: Use specific names like "Flash Sale Alert" or "Order Shipped Notification" instead of generic ones. This makes templates easy to find when building journeys.
**Add a helpful description**: Write a brief note about when you'll use this template. For example: "Sent when customers abandon their cart for 2+ hours" or "Welcome notification for new app users."
## Step 3: Craft your notification content
Now create the actual notification that customers will see on their devices:
**Write a compelling title**: This is your headline - make it count. Keep it under 50 characters for optimal display across devices. Examples:
* "Your order is on its way!"
* "50% off ends tonight!"
**Create engaging body text**: This is your main message. Keep it concise but informative, ideally under 120 characters. Focus on value and urgency:
* "Track your package and see estimated delivery time"
* "Don't miss out - shop your favorites before midnight"
**Add visual impact with images**: Upload an image that enhances your message. The image appears as a **rich notification attachment** in the OS tray — it is not your app's launcher icon or logo.
Use high-quality visuals that:
* Support your notification's purpose
* Maintain brand consistency
* Display well at small sizes
* Follow platform guidelines (JPEG, PNG, or GIF formats)
**Configure sound settings**:
* Leave the field empty for a silent notification.
* Enter `default` to use the device's default notification sound.
* Enter a bundled `.wav` filename such as `order_ready.wav` for a custom sound.
Custom notification audio cannot be uploaded to or streamed from Masivo. iOS
and Android require the file to be included in the installed mobile app. The
filename in the template must exactly match that bundled file. See the [mobile
integration
guide](/api-reference/guides/integrate-push-notification#custom-notification-sounds).
**Set up deep linking**: Add a URL that opens a specific screen in your app when users tap the notification:
* Product pages for promotional notifications
* Order tracking for shipping updates
* Specific app sections for engagement notifications
## Step 4: Optimize for different platforms
Use the real-time preview to see how your notification appears on both iOS and Android devices:
**iOS preview**: Check that your title and text display properly within iOS notification constraints.
**Android preview**: Verify that your content works well with Android's notification style and length limits.
**Make adjustments**: Refine your content based on how it appears in both previews to ensure optimal display across all devices.
## Step 5: Test and deploy your template
Before using your template in campaigns, it's important to test and understand its reach:
### Send test push notifications
Test your template to ensure it works correctly across different devices:
**Use the test function**: Click the **Send test** button in your template actions to send a preview to your test audience.
**Test audience requirements**: Your test group must have customers with registered device tokens. If your test audience is empty or has no device tokens, you'll need to add customers with mobile app installations first.
**Physical device testing**: Push notifications must be tested on actual devices with preview builds - they won't work with development servers or emulators.
A test send uses a different FCM `data` payload than a journey send: tests
set `notificationId` to `"test"` and omit `customerId`. Scheduled sends use
the delivery history id as `notificationId`. See [FCM data
payload](/api-reference/guides/integrate-push-notification#fcm-data-payload)
for the full JSON per origin.
### Schedule push notifications
Once your template is ready, you can schedule it for delivery:
**Schedule for later**: Use the **Schedule** button to set up automated delivery at specific times.
**Choose your audience**: Select which customer segment will receive your push notification campaign.
**Set timing**: Pick the optimal time for your audience based on their activity patterns and time zones.
The system tracks two key metrics for each audience: total audience size and
customers with device tokens. This helps you understand your potential push
notification reach before sending campaigns.
## A/B testing with variants
Use the **A/B testing** section to add up to four versions of the same
template (title, body, image, sound, or deep link). Scheduled sends can split
the audience by percentage. Journey push actions pick one variant.
See [Template variants](/guides/template-variants) for limits, distribution,
tests, and analytics.
## What you've accomplished
You've created a professional push notification template that captures
attention, communicates value, and drives user action. Your template is now
ready for testing and deployment to engage customers effectively through your
automated marketing workflows.
## Next steps
Once your push notification campaigns are running, you can track their performance and export analytics data:
* **Configure channel settings**: Review [Push settings](/guides/settings/push) to manage Firebase credentials and token duration
* **View analytics**: Navigate to **Analytics > Push** to see delivery rates, opens, and engagement metrics
* **Compare variants**: When a template has more than one version, the template analytics view includes a variants table. See [Template variants](/guides/template-variants#analytics)
* **Export data**: Learn how to [export analytics tables to CSV](/guides/analytics-exports) for deeper analysis and reporting
## Related architecture
* [Marketing & destinations](/resources/data-architecture#marketing-destinations) — How push templates are sent through journeys and audience campaigns
# Create rewards
Source: https://docs.masivo.ai/guides/create-rewards
Learn how to create rewards in Masivo
## Rewards in Masivo
To create a reward in Masivo, first navigate to the **Rewards** section in the dashboard. You can find them in the left sidebar.
To create a new reward, click the **New reward** button. Then choose the type of reward you want to create.
Each type of reward needs different information to be created. Learn how to configure each type below:
## Limiting redemption by tier
In the **E-commerce** tab, use **Tiers** to select which active customer tiers
may redeem the reward. Leave it empty to allow every tier and customers without
a tier. The tier scope is checked together with reward conditions.
Rewards already accumulated in customer wallets keep their saved snapshot. Use
the existing wallet update option when changing a reward if existing wallet
rewards must receive the new tier scope.
## Points Rewards
Points are usually given for purchases or other actions you want to encourage. Customers can accumulate these points and redeem them for rewards.
When creating a **Points** reward, you need to set the **Name** and the **Conversion factor**. The **Conversion factor** is the amount of money each point is worth. For example, if you set the **Conversion factor** to **0.10**, each point will be worth **0.10** dollars.
Want to see a step-by-step guide? [Watch how to configure points rewards](https://youtu.be/0p0eOCYXoZI?si=48HU6EDL3WsrEKjM).
## Discount Rewards
Discounts are a great way to encourage customers to make a purchase. You can offer discounts on order value, specific products, or shipping costs.
When creating a **Discount** reward, you can configure:
* **Discount type**: Percentage off or fixed amount off
* **Discount value**: The amount or percentage of the discount
* **Apply to**: Order value, product in order, or shipping
Want to see a step-by-step guide? [Watch how to configure discount rewards](https://youtu.be/v8U6KZiCQuk?si=SF1VtwVTKL51ZopY).
## Product Rewards
Product rewards are a great way to promote new products or clear out old stock. You can offer them in two ways: adding a product to the order or replacing a product in the order.
When creating a **Product** reward, you can configure which products to offer and any conditions that must be met for redemption.
Want to see a step-by-step guide? [Watch how to configure product rewards](https://youtu.be/gOmL9C-r6mQ?si=IW60IQdm4A6Hxify).
That's it! You have created a reward that your customers can start
accumulating.
## Next steps
Once your rewards are active, you can track how customers are using them:
* **View analytics**: Navigate to **Analytics > Rewards** to see redemption rates, popular rewards, and customer engagement
* **Export data**: Learn how to [export analytics tables to CSV](/guides/analytics-exports) for deeper analysis and reporting on reward performance
## Related architecture
* [Activity & events](/resources/data-architecture#activity-events) — How rewards are issued and stored through the loyalty event pipeline
# WhatsApp templates
Source: https://docs.masivo.ai/guides/create-whatsapp
Learn how to create and manage WhatsApp templates in Masivo
## What you'll learn
By the end of this guide, you'll know how to create template messages, get
them approved, and use them in campaigns and journeys.
Before creating templates, configure your WhatsApp channel in
[WhatsApp settings](/guides/settings/whatsapp).
## Step 1: Access WhatsApp templates
Navigate to **Marketing Automation > WhatsApp templates**.
Click **New WhatsApp template** to create your first template.
## Step 2: Define your template foundation
Start by organizing your template so it is easy to find and reuse later.
* **Name**: Use clear names like `Cart Recovery - 1 hour`.
* **Description**: Add context about when this template is sent.
* **Brand**: Select the correct brand if you manage multiple brands.
* **Template ID**: Use a stable identifier (lowercase, numbers, underscores).
* **Category**: Choose the right type (`MARKETING`, `UTILITY`,
`AUTHENTICATION`).
* **WhatsApp phone number**: Pick the sending number for this template.
## Step 3: Build the message content
Create the content based on your selected category:
* **Header**: Optional text or media (depending on category rules).
* **Body**: Main message content with dynamic variables where needed.
* **Footer**: Optional supporting text.
* **Buttons**: Quick replies, URLs, phone actions, or copy code (when
supported).
Use variables to personalize messages, but keep content compliant with WhatsApp
template guidelines.
## Step 4: Submit and monitor approval status
When you save a template, Masivo submits it to Meta and tracks approval status.
Common statuses:
* **PENDING**: Submitted and waiting for review.
* **APPROVED**: Ready to use in sends and journeys.
* **REJECTED**: Needs edits before re-submission.
Templates must be approved before you can send tests or schedule WhatsApp
campaigns.
## Step 5: Test and schedule WhatsApp campaigns
After approval, use template actions to validate and launch sends.
* **Send test**: Confirm formatting, variables, and button behavior.
* **Schedule**: Select audience and delivery time for campaign execution.
## A/B testing with variants
Use the **A/B testing** section to add up to four versions. Each WhatsApp
variant has its own Meta template ID. On save, Masivo submits each variant to
Meta for approval. Scheduled sends can split the audience by percentage.
Journey WhatsApp actions pick one approved variant.
See [Template variants](/guides/template-variants) for limits, distribution,
tests, and analytics.
## Step 6: Use templates in journeys and analytics
You can also use approved templates inside journeys with the
**Send WhatsApp message** action.
Then monitor performance in **Analytics > WhatsApp** to track delivery, read
rate, link opens, and attributed outcomes.
## What you've accomplished
You've created a reusable approved template and enabled campaign and journey
execution through Masivo's marketing automation flow.
## Next steps
* Configure channel setup in [WhatsApp settings](/guides/settings/whatsapp)
* Learn how to automate sends in [Journeys](/guides/create-journeys)
* A/B test copy with [template variants](/guides/template-variants)
* Personalize message content with
[template variables](/guides/using-template-variables)
* Export performance data with [Analytics Exports](/guides/analytics-exports)
## Related architecture
* [Marketing & destinations](/resources/data-architecture#marketing-destinations) — How WhatsApp templates are delivered in journeys and audience sends
# Create your first account
Source: https://docs.masivo.ai/guides/create-your-first-account
Learn how to create your first account in Masivo
## Welcome to Masivo!
To create your first account in Masivo, first go to the [https://app.masivo.ai](https://app.masivo.ai).
Then you will need to register if its your first time using Masivo. If you already have an account, you can log in with your credentials.
Now click on the **Create your first Account** button and fill in the required information.
Once ready, click on the **Submit** button.
That's it! You have created your first account in Masivo.
Now you can start configuring your loyalty program.
Want to see an overview of account settings? [Watch this video](https://youtu.be/TePWCevU0v8?si=WVITGyBpvBJwVOZV).
If you'd reached the maximum number of accounts, you [can contact us](mailto:soporte@trade.ec?subject=Please%2C%20increase%20my%20accounts%20limit) to increase the limit.
# Custom event types
Source: https://docs.masivo.ai/guides/custom-event-types
Define a Fields schema for custom event types in the Masivo dashboard.
Custom event types let you declare the payload fields you expect when customers perform actions that are not covered by default events (for example product views or survey submissions). You define those fields in the dashboard as a **Fields schema**.
## Define a Fields schema
In the dashboard go to **Events → Custom event types**, then click **New** or open an existing type to edit.
In the **Fields schema** section, add one row per field. Each row has:
* **Field Name** — the property path you will send on the event (letters, numbers, hyphens, underscores; use dots for nesting, for example `order.value`)
* **Type** — one of `string`, `number`, `boolean`, or `unknown`
Save the custom event type. The schema is stored with the type and shown in the Custom event types list.
## Schema example
A Fields schema is an array of `{ name, type }` objects. For example:
```json theme={null}
[
{ "name": "product_id", "type": "string" },
{ "name": "order.value", "type": "number" },
{ "name": "is_premium", "type": "boolean" }
]
```
| Type | Meaning |
| - | - |
| `string` | Text values |
| `number` | Numeric values |
| `boolean` | `true` / `false` |
| `unknown` | No fixed primitive type |
The schema is optional. If you do not add fields, the custom event type still
works as a trigger; it simply has no declared payload shape.
## Related
* [Events (CDP)](/guides/CDP/events) — how events fit into the Customer Data Platform
* [Launch a campaign](/guides/launch-a-campaign) — use a custom event type as a campaign trigger
* [Emit event](/api-reference/behavior/events/post) — send events with your custom `type`
# Best practices
Source: https://docs.masivo.ai/guides/emails/best-practices
Protect sender reputation and monitor Gmail deliverability
## Authenticate every sending domain
Configure the SPF and DKIM records shown under **Marketing Automation >
Settings > Email**, then publish a DMARC record for the From domain. Verify the
domain before sending production traffic.
Google requires SPF or DKIM for all senders and SPF, DKIM, and DMARC for bulk
senders. Keep the From domain aligned with SPF or DKIM. See Google's current
[email sender guidelines](https://support.google.com/mail/answer/81126).
## Send expected email
* Send marketing email only to recipients who opted in.
* Do not use purchased, rented, or scraped lists.
* Keep volume increases gradual and avoid abrupt spikes.
* Suppress hard bounces, complaints, and unsubscribed recipients immediately.
* Keep transactional and promotional traffic logically separate.
## Make unsubscribe easy
Include `{{global.unsubscribe}}` visibly in every marketing template. Masivo
also sends RFC 8058 `List-Unsubscribe` and `List-Unsubscribe-Post` headers, so
supporting inboxes can perform a one-click unsubscribe without opening the
message.
Transactional account emails are not given marketing unsubscribe headers.
## Monitor Postmaster and Masivo together
Masivo analytics reports campaign delivery, bounce, complaint, open, and click
events. Google Postmaster reports Gmail's domain-level view. Connect it under
**Settings > Email**, then use **Analytics > Emails > Deliverability** to review:
* Compliance verdicts for SPF, DKIM, DMARC, DNS, TLS, spam, and unsubscribe
* User-reported spam rate
* Authentication and TLS success rates
* Delivery error types and reasons
* Feedback loop IDs
Postmaster data is delayed and can be missing below Google's reporting
thresholds. Treat missing data as unavailable, not as a zero rate.
## React to problems
* Rising spam: pause the affected segment and review consent and content.
* Authentication failure: correct DNS and From-domain alignment before resuming.
* Reputation-related rejects: reduce volume and remove low-engagement recipients.
* Temporary failures: avoid immediate high-volume retries.
* Unsubscribe failure: verify the visible link and one-click endpoint.
Do not use synthetic warm-up or engagement services. They create traffic that
recipients did not request and can damage the same reputation they claim to
improve.
# Creating an email template
Source: https://docs.masivo.ai/guides/emails/create-email
Learn how to create and manage email templates in Masivo
## What you'll learn
By the end of this guide, you'll know how to create professional email templates that convert customers and integrate seamlessly with your automated marketing workflows.
## Step 1: Access the email templates section
Navigate to **Marketing Automation > Email templates** in your dashboard. This is where you'll manage all your reusable email designs.
Click the **New email template** button to start creating your first template.
## Step 2: Configure your template foundation
Start by setting up the basic information that will help you organize and identify your template:
**Choose a descriptive name**: Use names like "Welcome Series - Email 1" or "Black Friday Promotion" instead of generic names. This makes templates easier to find later.
**Add a helpful description**: Write a brief note about when and why you'll use this template. For example: "Sent to new customers 24 hours after registration."
**Select the right brand**: Choose which brand this template represents. This ensures consistent styling and helps organize templates across multiple brands.
## Step 3: Build your email content
Now comes the creative part - designing your email using the visual editor.
### Start with structure
Before adding content, plan your email layout:
1. **Add a Row** from the Components panel to create your first section
2. **Insert Columns** within the row to organize content side-by-side
3. **Use Spacers** between sections to create visual breathing room
### Add compelling content
**Text elements**: Start with a clear headline that grabs attention. Use the Text component to add your main message, keeping paragraphs short and scannable.
**Images**: Add visual interest with the Image component. Use high-quality photos that support your message and maintain your brand aesthetic.
**Call-to-action buttons**: Every email needs a clear next step. Use the Button component to create compelling CTAs like "Shop Now" or "Learn More."
**Links**: Add clickable links to direct readers to specific pages or content that supports your email's goal.
### Use pre-built sections for speed
Save time by using pre-designed sections:
**Gallery sections**: Perfect for showcasing multiple products or highlighting different features.
**Ecommerce layouts**: Use these when promoting products, complete with pricing and purchase buttons.
**Article sections**: Ideal for newsletters, blog updates, or educational content.
**Header and Footer**: Maintain brand consistency with professional headers and compliant footers.
### Start with proven templates
If you're new to email design, start with these tested templates:
**Promotion templates**: Pre-optimized for sales and special offers with proven conversion elements.
**Welcome templates**: Designed to make great first impressions on new customers.
**Feedback templates**: Structured to encourage customer reviews and survey responses.
## Step 4: Optimize for all devices
Use the preview toggles to check how your email looks on:
* **Desktop**: Ensure your layout works on wide screens
* **Mobile**: Verify text is readable and buttons are easily tappable
Make adjustments until your email looks great on both devices.
## Step 5: Add advanced customization (optional)
For specific requirements, use the **Custom HTML** option to add:
* Advanced styling
* Custom fonts
* Interactive elements
* Tracking pixels
## Step 6: Test and deploy your template
Before using your template in campaigns, it's important to test and understand its reach:
### Send test emails
Test your template to ensure it displays correctly across different email clients:
**Use the test function**: Click the **Send test** button in your template actions to send a preview to your test audience.
**Test audience requirements**: Your test group must have customers with valid email addresses. If your test audience is empty, you'll need to add customers first.
**Review across devices**: Test how your email appears on desktop, mobile, and tablet devices to ensure optimal display.
### Schedule email campaigns
Once your template is ready, you can schedule it for delivery:
**Schedule for later**: Use the **Schedule** button to set up automated delivery at specific times.
**Choose your audience**: Select which customer segment will receive your email campaign.
**Set timing**: Pick the optimal time for your audience based on their behavior patterns and time zones.
Monitor your audience size and email deliverability rates to optimize your campaigns. The system shows you exactly how many customers will receive each email before you send it.
## A/B testing with variants
Use the **A/B testing** section to add up to four versions of the same
template (subject, preheader, sender name, or design). Scheduled sends can
split the audience by percentage. Journey email actions pick one variant.
See [Template variants](/guides/template-variants) for limits, distribution,
tests, and analytics.
## What you've accomplished
You've created a professional email template that maintains brand consistency,
engages customers effectively, and integrates seamlessly with your automated
marketing workflows. Your template is now ready for testing and deployment to
reach your customers at the right moments.
## Next steps
Once your email campaigns are running, you can track their performance and export analytics data:
* **Configure channel settings**: Review [Email settings](/guides/settings/email) for domains, footer defaults, and tracking options
* **View analytics**: Navigate to **Analytics > Emails** to see delivery rates, opens, clicks, and conversions
* **Compare variants**: When a template has more than one version, the template analytics view includes a variants table. See [Template variants](/guides/template-variants#analytics)
* **Export data**: Learn how to [export analytics tables to CSV](/guides/analytics-exports) for deeper analysis and reporting
## Related architecture
* [Marketing & destinations](/resources/data-architecture#marketing-destinations) — How email templates are used in journeys and audience campaigns
# Unsubscribe links
Source: https://docs.masivo.ai/guides/emails/unsubscribe-links
Understand why including dynamic unsubscribe links is essential for compliance and deliverability
Marketing email must provide a clear opt-out. Transactional account email is
excluded from marketing unsubscribe behavior.
## Overview
Every marketing email must include a way for recipients to opt out. This is a
key part of deliverability and maintaining a healthy audience.
This guide explains why dynamic unsubscribe links are essential, and how they relate to practices like audience hygiene and domain warm-up.
## Why unsubscribe links matter
Failing to include an unsubscribe link can result in:
* Emails marked as spam or phishing
* Poor deliverability rates
* Blacklisted domains or IP addresses
* Provider enforcement or account suspension
Including a proper unsubscribe option builds trust with your recipients and protects your sender reputation.
## How unsubscribe links help during domain warm-up
When warming up your domain or IP, your goal is to establish a strong sender reputation by gradually increasing your volume and avoiding spam complaints.
While unsubscribe links are not explicitly mentioned in warm-up schedules, they play a key role in:
* Keeping user-reported spam low
* Giving users a clear and respectful way to opt out
* Preventing harmful engagement patterns that damage reputation
This aligns with Google's recommendation to keep spam rates below 0.1% and
avoid reaching 0.3% or higher.
## What is a dynamic unsubscribe link?
Masivo supports template variables like `{{unsubscribe_link}}`, which are replaced at send time with a personalized unsubscribe URL for each recipient.
This ensures:
* Users can be safely and uniquely identified
* Unsubscribe requests are properly handled and tracked
* You stay compliant with anti-spam regulations like CAN-SPAM and GDPR
Masivo also adds personalized RFC 8058 headers to marketing email:
* `List-Unsubscribe` contains a signed recipient URL.
* `List-Unsubscribe-Post: List-Unsubscribe=One-Click` allows Gmail and other
supporting inboxes to unsubscribe with a POST request.
The POST is idempotent, so repeated provider requests remain successful without
resubscribing or duplicating state.
Never use a static or shared unsubscribe URL. Always insert ` {{ unsubscribe_link }}` inside your email body to personalize the opt-out link
for each recipient.
## How to configure the unsubscribe link
To properly insert the unsubscribe link in your emails, follow these steps using the Masivo Email Builder.
### 1. Add the unsubscribe link to the footer
Use the footer editor to insert a new line with an unsubscribe link in both desktop and mobile versions.
### 2. Use the dynamic variable in the snippet
Edit the Unsubscribe component to include the dynamic variable. Make sure the snippet uses the dynamic placeholder:
`{{global.unsubscribe}}`
This ensures that each recipient gets a personalized unsubscribe URL.
The variable `{{global.unsubscribe}}` must be present in your template for Masivo to track and honor unsubscribe requests correctly.
# Integrate in-app messages
Source: https://docs.masivo.ai/guides/in-app-messages
Step-by-step guide to integrating in-app messages in your mobile app
In-app messages appear inside your app UI. This guide covers fetching eligible messages, rendering them, and reporting impressions to Masivo.
## Prerequisites
* An active Masivo account with at least one **ACTIVE** in-app template
* A **SERVER** or **CLIENT** API key
* Customers registered with `external_id` values matching your app user ids
* For **real-time journey delivery**: a registered FCM device token and push consent on the customer profile (same setup as [push notifications](/guides/create-push)). Without them, messages still materialize in Masivo but the app only sees them on the next `GET /inapp-messages` fetch (for example on app open).
## Check capabilities
Before wiring the SDK, confirm in-app is enabled and read journey event types that support real-time refresh:
```bash theme={null}
GET /api/storefront/v1/inapp/capabilities
Authorization: Bearer
x-account-id:
```
Example response:
```json theme={null}
{
"data": {
"inappEnabled": true,
"eventTypes": ["PURCHASE", "APP_OPEN"]
}
}
```
When `inappEnabled` is `false`, skip in-app fetch calls until templates are published.
## Fetch eligible messages
Call this endpoint after app launch and whenever a trigger event from `eventTypes` fires:
```bash theme={null}
GET /api/storefront/v1/customers/{customer_id}/inapp-messages?limit=5
Authorization: Bearer
x-account-id:
```
Optional query parameters:
| Parameter | Description |
| - | - |
| `brand_id` | Filter by brand |
| `trigger` | Event type active on screen (returns untriggered or matching messages) |
| `types` | Comma-separated types, for example `modal,banner` |
| `limit` | Max messages (1–50, default 10) |
Example response:
```json theme={null}
{
"data": {
"messages": [
{
"id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"template_id": "f47ac10b-58cc-4372-a567-0e02b2c3d479",
"type": "modal",
"priority": 10,
"expires_at": "2026-08-01T00:00:00Z",
"content": {
"title": "Welcome back",
"body": "You have 500 points ready to redeem.",
"image_url": "https://cdn.example.com/hero.png",
"buttons": [
{
"id": "primary",
"text": "Redeem now",
"action": "deeplink",
"value": "myapp://rewards"
},
{
"id": "dismiss",
"text": "Not now",
"action": "dismiss"
}
]
},
"display_rules": {
"max_impressions": 3,
"priority": 10
},
"trigger": {
"event": "APP_OPEN"
}
}
]
}
}
```
## Render messages
Messages are pre-sorted by Masivo, but you can re-sort using
`display_rules.priority` when showing multiple surfaces.
Render `modal`, `banner`, `slideup`, or `fullscreen` using your design
system. Use `content.buttons` for actions.
Honor `max_impressions` client-side as a fallback, but always log events so
Masivo enforces caps server-side.
## Log impression events
After displaying or interacting with a message, report the action:
```bash theme={null}
POST /api/storefront/v1/customers/{customer_id}/inapp-messages/{message_id}/events
Authorization: Bearer
x-account-id:
Content-Type: application/json
{
"action": "shown"
}
```
Supported actions: `shown`, `clicked`, `dismissed`.
For button clicks, include metadata:
```json theme={null}
{
"action": "clicked",
"metadata": {
"button_id": "primary"
}
}
```
Masivo returns `{ "data": { "success": true } }` and emits `INAPP_SHOWN`, `INAPP_CLICKED`, or `INAPP_DISMISSED` tracking events.
## Common patterns
### Refresh on app open
```typescript theme={null}
const refreshInApp = async (customerId: string) => {
const res = await fetch(
`https://app.masivo.ai/api/storefront/v1/customers/${customerId}/inapp-messages`,
{
headers: { Authorization: `Bearer ${apiKey}`, "x-account-id": accountId }
}
);
const { data } = await res.json();
return data.messages;
};
```
### Refresh after a journey trigger event
```typescript theme={null}
const onEvent = async (customerId: string, eventType: string) => {
const caps = await fetchCapabilities();
if (!caps.eventTypes.includes(eventType)) return;
const query = new URLSearchParams({ trigger: eventType });
const res = await fetch(
`https://app.masivo.ai/api/storefront/v1/customers/${customerId}/inapp-messages?${query}`,
{
headers: { Authorization: `Bearer ${apiKey}`, "x-account-id": accountId }
}
);
const { data } = await res.json();
showNextMessage(data.messages[0]);
};
```
### Log shown before render
Always send `shown` after the message is visible. Masivo assigns a `tracking_id` on `shown` and reuses it for subsequent `clicked` or `dismissed` events in the same impression.
## Frequently asked questions
**Displaying** messages does not — your app fetches eligible messages over HTTPS and renders them in your UI.
**Real-time journey delivery** does. When a journey materializes an in-app message, Masivo sends a silent push so the app refreshes its queue. That path requires:
* A registered FCM **device token** on the customer (`PATCH /customers/{id}/devices`)
* **Push consent** on the customer profile (`consent.purposes.push_notifications` not disabled)
* Firebase configured in [Push settings](/guides/settings/push)
If either is missing, Masivo skips the silent push. The message remains in the database and appears on the next fetch (for example when the user opens the app).
The message is excluded from `GET /inapp-messages` results. A final `shown` event moves the row out of `pending` when the cap is hit.
Yes, when `display_rules.max_impressions` is greater than 1 and `min_interval_seconds` has elapsed since the last `shown` event.
When a qualifying event is processed, Masivo materializes the in-app message and sends a **silent push** to devices with a valid FCM token and push consent. Your app handles that payload, then calls `GET /inapp-messages` (optionally with the `trigger` query param from the push data). Check `eventTypes` from `/inapp/capabilities` for supported journey events.
## Related documentation
* [In-app messages](/guides/create-in-app-templates)
* [In-app messages concept](/concepts/in-app-messages)
* [Integrate in-app messages](/api-reference/guides/integrate-in-app-messages)
# Launch a campaign
Source: https://docs.masivo.ai/guides/launch-a-campaign
Learn how to launch a campaign in Masivo
## Campaigns in Masivo
To launch a campaign in Masivo, first navigate to the **Campaigns** section in the dashboard. You can find them in the left sidebar.
## Creating a new campaign
In the campaigns section you will find a Gantt chart with all the campaigns you have created. To create a new campaign, click the **New campaign** button.
Now select the type of campaign you want to create. For this example, we will create a **Behavior** campaign.
Masivo supports different types of campaigns. Want to see how to create a coupon campaign? [Watch how to create coupon campaigns](https://youtu.be/f__1dCqpa0k?si=-LWdUwPDN2-THQXr).
First let's set the **Name** of the campaign and the **Start date** and **End date**. Only during this period the campaign will offer rewards to your customers.
We recommend including the rewards name that will be given and at least the
event that will trigger the campaign
For this example, we will **name** it "Earn points for each purchase." We will set the **start date** to today and ensure that the campaign does not have an **end date**.
## Choosing a trigger
After setting the campaign details, you need to choose the **Trigger** that will activate the campaign. In this case, we will choose the **Purchase** trigger. This means that every time a customer makes a purchase, they will receive points.
There are many more triggers you can choose from. You can also create your own custom triggers, just go to **Events → Custom event types**. See [Custom event types](/guides/custom-event-types) to define a Fields schema.
Most events, such as purchases, will not trigger automatically. You need to
integrate your e-commerce service with Masivo to send us these events. We will
take care of the rest.
## Limiting eligibility by tier
In the **E-commerce** tab, use **Tiers** to limit the whole campaign to one or
more active customer tiers. Leave it empty if the campaign should work for every
tier. This global scope is checked before the rules and is independent from the
singular **Customer tier** condition inside a rule.
## Setting rules
After choosing the trigger, you need to set the rules for the campaign. In this case, we will set the **Points** reward. We will give **1 point** for every **1 dollar** spent.
If you've been following along, you should now have a reward of type **Points** and a tier pyramid of Bronce, Silver, and Gold. If not, you can create them now.
### Effects
When you create a rule you will find two sections: **Effects** and **Conditions**. In the **Effects** section, you can set the reward that the customer will receive depending on which tier they are in.
For the **Bronce** tier, we will give *1 point* for every *1 dollar* spent. For the **Silver** tier, we will give *2 points* for every *1 dollar* spent. And for the **Gold** tier, we will give *3 points* for every *1 dollar* spent.
To make the rewards by dynamically calculated by each dollar spent. Select the **Dynamic multiplier** box and set the **Multiplier** to **Order value**.
Masivo lets you set the rewards expiration date for each tier of each rule.
Use this feature to **create urgency** and encourage your customers to **make
a purchase**.
### Conditions
For this example, we will not set any conditions. This means that the rule will apply to all customers who make a purchase. To learn more about conditions, check out our guide on [Advance rules](/guides/advance-rules).
## Launching the campaign
Now, click **Save** until you are back in the **New campaign** page.
Finally, set a estimated **Budget** for the campaign. This will help you keep track of how much money you are giving away with each reward.
**Budgets will not cap your campaigns** when the cost of the total rewards
redeemed reaches the budget. They are only meant to serve as a visual guide
for how much you are spending on each campaign.
Click **Save** and you are done!
Your campaign is now live and ready to reward your customers.
In the Gantt chart, you will see the campaign you just created.
## Next steps
Once your campaign is running, you can track its performance and analyze the results:
* **View analytics**: Navigate to **Analytics > Campaigns** to see how many rewards have been distributed, engagement rates, and ROI
* **Export data**: Learn how to [export analytics tables to CSV](/guides/analytics-exports) for deeper analysis and reporting on campaign performance
## Related architecture
* [Activity & events](/resources/data-architecture#activity-events) — How campaign matching updates wallet, tier, and profile traits
# Manage teams
Source: https://docs.masivo.ai/guides/manage-teams
Learn how to create teams, assign members, and scope resources in Masivo
Teams help large organizations separate analytics visibility without creating multiple Masivo accounts. This guide walks through team setup and day-to-day management.
## Prerequisites
* Account member with `settings:write` to create teams or assign members
* `settings:delete` to remove members
## Create a team
Go to **Settings → Team** and select the **Teams** tab, or navigate directly to `/settings/teams`.
Click **New team**. Enter a **name** and optional **description**.
The team is created with `ACTIVE` status. Open the team detail page to see assigned members.
## Assign members to teams
1. Open **Settings → Team → Members**.
2. Find the member row and open the **Teams** column editor.
3. Select one or more teams, or clear all teams for account-wide-only visibility rules.
Members with the **ADMIN** role or the account owner show **All access** and cannot be restricted by team filters.
## Scope a resource to teams
When creating or editing a campaign, reward, journey, or marketing template:
1. Locate the **Teams** field in the form.
2. Select the teams that should see this resource in analytics.
3. Leave **No teams** selected to keep the resource visible to everyone.
## Assign people to an entity
Stores, brands, campaigns, journeys, rewards, and integrations have an optional
**Assigned people** field. Open the create or edit form and select one or more
members of the current account. For rewards, the field is in **Advanced**;
for the other entities, it is in the basic settings.
Save the form to record responsibility, or clear the selection to leave the
entity unassigned. Assignments do not grant permissions, change team visibility,
or inherit from a brand. Removing someone from the account also removes their
assignments in that account. Tickets continue to use their existing assignees.
The field is stored as `assigned_user_ids`, an array of user UUIDs that defaults
to `[]`. Writes reject duplicate IDs and users from another account. Updates
that omit the field preserve existing assignments; sending `[]` clears them.
## Invite a new member
1. On **Settings → Team → Members**, click **Invite member**.
2. Enter the email address. Masivo sends an invitation valid for 24 hours.
3. After acceptance, assign a **role** and **teams** to the new member.
Track pending invitations under **Settings → Team → Invitations**.
## Leave the account
Non-owner members can leave from the **Leave team** action on the Members tab. The account owner cannot leave without transferring ownership first.
## Related documentation
* [Teams concept](/concepts/teams)
* [Transfer account ownership](/guides/transfer-account-ownership)
# General
Source: https://docs.masivo.ai/guides/marketing-automations
Learn what marketing automation is and how it can transform your customer engagement
## Marketing automation in Masivo
Marketing automation allows you to create automated workflows that send messages to customers based on their behavior and predefined conditions. Instead of manually sending communications, you set up rules that trigger actions automatically.
To access marketing automation features, navigate to the **Marketing Automation** section in the dashboard. You can find it in the left sidebar.
## How marketing automation works
Marketing automation in Masivo consists of seven main components:
**Journeys**, **Push notifications**, **WhatsApp**, **Emails**,
**Links**, and **Settings**. These work together to create automated customer
communication flows.
Links are trackable URLs that you can use in your automated communications
to measure engagement and drive specific actions.
Settings centralize channel-level configuration for links, email, push, and
WhatsApp.
Push templates define the content structure for your app notifications.
WhatsApp templates let you send approved message flows through Meta.
Email templates help you build branded campaigns with reusable layouts.
Journeys are automated workflows that define what happens when specific
events occur.
A/B test email, push, WhatsApp, in-app, and News Feed templates, then pick
a version in journeys or split traffic on scheduled sends.
## Next steps
Once your marketing automation campaigns are running, you can measure their performance:
* **View real-time analytics**: Navigate to the **Analytics** section to monitor delivery rates, opens, clicks, and conversions across all your campaigns
* **Export data for analysis**: [Export analytics tables to CSV](/guides/analytics-exports) to analyze campaign performance, identify trends, and create custom reports
## Related architecture
* [Marketing & destinations](/resources/data-architecture#marketing-destinations) — How audiences, journeys, and channel messages work together
# News Feed cards
Source: https://docs.masivo.ai/guides/news-feed
Create, publish, display, and track persistent in-app cards.
This guide shows how to configure a News Feed template in the dashboard and integrate the public feed and interaction endpoints in your app.
## Prerequisites
Authenticate with the Storefront API using a Bearer access token. See [Integrate with Masivo](/api-reference/guides/integrate-with-masivo).
These endpoints work with **CLIENT** and **SERVER** API keys. Use a SERVER
key when fetching the feed from your backend; CLIENT keys are suitable for
embedded apps that call Masivo directly.
* A customer registered with the same id your app sends to Masivo
* A native or web feed surface that can render the returned card content
* For journey publication, an active journey with a News Feed action
## Create and publish a card
In the dashboard, go to **Marketing Automation → News Feed** and select **New template**.
Select `classic`, `captioned_image`, or `image_only`. Complete the title, message, image, and optional link fields required by that choice.
Set the priority, pinned state, and expiration in days. Pinned cards are returned first; priority orders cards within the same pinned group.
Use the template in a journey News Feed action or schedule it for an audience. Audience eligibility is evaluated when each customer fetches the feed.
## A/B testing with variants
Use the **A/B testing** section in the template editor to add up to four
versions (card type, copy, image, link, pinned state, priority, or
expiration). Scheduled sends can split the audience by percentage. Journey
News Feed actions pick one variant.
See [Template variants](/guides/template-variants) for limits, distribution,
tests, and analytics.
## Display the feed
Fetch eligible cards from your backend or trusted SDK client:
```bash theme={null}
curl --request GET \
--url 'https://app.masivo.ai/api/storefront/v1/customers/customer_123/news-feed?brand_id=main&limit=20' \
--header 'Authorization: Bearer ' \
--header 'x-account-id: '
```
A complete card response has this shape:
```json theme={null}
{
"data": {
"messages": [
{
"id": "90e3b315-c8b6-4055-835d-93a8300ea52e",
"type": "news_feed",
"campaign_id": "85cd7c9a-97bd-4235-ab56-1a9b9d6fd6fd",
"priority": 10,
"expires_at": "2026-08-19T15:30:00.000Z",
"content": {
"title": "Your weekly offer",
"body": "Save 20% on your next order.",
"image_url": "https://cdn.example.com/offers/weekly.png",
"link": "myapp://offers/weekly",
"link_text": "View offer",
"pinned": true
},
"display_rules": {
"priority": 10
}
}
]
}
}
```
Fields without values are omitted. `expires_at` and `campaign_id` can be absent when the assignment or template does not define them.
## Track customer interactions
Log each interaction with the template id returned as the card `id`:
```bash theme={null}
curl --request POST \
--url 'https://app.masivo.ai/api/storefront/v1/customers/customer_123/news-feed/90e3b315-c8b6-4055-835d-93a8300ea52e/events' \
--header 'Authorization: Bearer ' \
--header 'Content-Type: application/json' \
--header 'x-account-id: ' \
--data '{"action":"shown"}'
```
Valid actions are `shown`, `clicked`, and `dismissed`. A dismissal removes the card from subsequent responses unless the same template receives a newer assignment.
## React Native SDK
Assignments reference the template, not a content copy. Editing an active
template updates what every assigned customer sees immediately, with no
republish step — the next fetch returns the new content. Refetch the feed
whenever the customer is likely to see stale data instead of relying on a
single fetch per session.
```ts theme={null}
const cards = await masivo.inApp.fetchNewsFeedCards({
customerId: "customer_123",
brandId: "main",
limit: 20
});
for (const card of cards) {
await masivo.inApp.logNewsFeedCardShown(card, {
customer_id: "customer_123",
brand_id: "main"
});
}
```
When a customer taps or dismisses a card:
```ts theme={null}
const event = { customer_id: "customer_123", brand_id: "main" };
await masivo.inApp.logNewsFeedCardClicked(card, event);
await masivo.inApp.logNewsFeedCardDismissed(card, event);
```
The SDK keeps News Feed fetches separate from the overlay in-app message queue and returns pinned cards before unpinned cards.
## Common patterns
### Refetch when the app opens or returns to the foreground
Since card content is resolved live, fetch on launch and again whenever the app resumes from the background so customers always see the latest template content:
```ts theme={null}
import { AppState } from "react-native";
useEffect(() => {
const fetchFeed = () => {
masivo.inApp.fetchNewsFeedCards({
customerId: "customer_123",
brandId: "main",
limit: 20
});
};
fetchFeed();
const subscription = AppState.addEventListener("change", nextState => {
if (nextState === "active") fetchFeed();
});
return () => subscription.remove();
}, []);
```
### Remove expired cards locally
```ts theme={null}
const visibleCards = cards.filter(card => {
if (!card.expires_at) return true;
return new Date(card.expires_at).getTime() > Date.now();
});
```
### Open an optional destination
```ts theme={null}
const destination = card.content.link;
if (destination) {
await Linking.openURL(destination);
}
```
### Render link text with a fallback
```ts theme={null}
const linkLabel = card.content.link_text ?? "Learn more";
```
## Frequently asked questions
Yes. Assignments reference the template, and the Storefront endpoint resolves active template content on every request.
Audience membership is checked during feed resolution. The audience assignment stops making the card eligible immediately, unless another direct or global assignment still covers it.
Yes. Feed resolution collapses active assignments to one card per template. The latest assignment controls when a previously dismissed card can reappear, while any non-expiring assignment keeps it non-expiring.
Expired assignments are excluded immediately. The News Feed cleanup cron later deletes them in batches and removes orphaned interaction state after a seven-day grace period.
No. `shown` and `clicked` only update interaction timestamps. Only `dismissed` hides the card.
Pinned cards come first, followed by higher priority and then newer assignment time. The React Native SDK preserves this order while grouping pinned cards first.
# Next steps
Source: https://docs.masivo.ai/guides/next-steps
Learn what to do next after setting up your account
Now that you have grasped the basics of Masivo, it's time to take the next steps. Here are some things you can do to get the most out of your account.
## Prepare to go live
* Fine tune your tier system. Now, you have a more clear idea of how you can use tiers to reward your customers.
* Try new types of rewards. There are many ingenious ways to reward your customers. Try them out and see what works best for your business.
* Set up new campaigns. A popular one is onboarding new customers with a welcome bonus.
* Explore rules conditions. Do you feel ready to setup a double points on Tuesdays campaign? Or maybe a 10% discount when the order total is over \$100?
## Integrate your services with Masivo
* Read our [API documentation](/api-reference) to learn how to integrate your services with Masivo.
* Test your program. Make a few test purchases to see how the rewards are being earned and spent.
# Purchase Attribution System
Source: https://docs.masivo.ai/guides/purchase-attribution
Complete guide to implementing and understanding Masivo's purchase attribution system - track marketing campaign effectiveness, loyalty program impact, and measure ROI across all customer touchpoints
For outbound integrations, forward captured `attribution` on the purchase.
Masivo report calculations do not fill missing UTM in provider payloads.
See the [shared attribution guide](/api-reference/guides/attribution-tracking).
## Overview
Masivo's Purchase Attribution System automatically tracks and attributes customer purchases to their originating marketing touchpoints, whether from marketing automation campaigns (links, emails, push notifications) or loyalty program engagement. This system enables you to measure campaign effectiveness, calculate ROI, and understand which channels drive conversions.
Attribution is calculated automatically based on tracking IDs, loyalty
transactions, and configured attribution windows. This guide covers setup,
configuration, and implementation best practices.
## How Attribution Works
The attribution system operates in three stages:
1. **Event Collection**: Purchases and touchpoints (link clicks, reward accumulations) are tracked with attribution data
2. **Attribution Calculation**: System matches purchases to their originating touchpoints within configured time windows
3. **Performance Analysis**: Attribution data powers analytics and ROI calculations
### Attribution Flow Diagram
```
Marketing Touchpoint Purchase Event Attribution Record
------------------ --------------- -------------------
Link Open → Customer → Campaign Performance
├─ tracking_id ├─ Same tracking_id ├─ Revenue attributed
├─ UTM parameters ├─ Purchase data ├─ Time to conversion
└─ Timestamp └─ Timestamp └─ Campaign details
OR
Loyalty Accumulation → Purchase Event → Loyalty Attribution
├─ Reward earned ├─ event_id ├─ Revenue attributed
├─ event_id ├─ Redemption ├─ Time to conversion
└─ Timestamp └─ Timestamp └─ Loyalty impact
```
## Attribution Types
Masivo supports two primary attribution types:
### 1. Marketing Attribution
Tracks purchases that originated from marketing campaigns including:
* **Links/Deeplinks**: Short URLs with tracking parameters
* **Email Campaigns**: Email templates sent via marketing automation
* **Push Notifications**: Push campaigns with tracking
**Key Identifier**: `tracking_id` - Links the customer's journey from click to purchase
### 2. Loyalty Attribution
Tracks purchases influenced by loyalty program engagement:
* **Reward Accumulation**: Customer earned rewards before purchase
* **Reward Redemption**: Customer redeemed rewards during purchase
**Key Identifier**: `event_id` - Links reward transactions to purchase events
## Setting Up Attribution
### Step 1: Configure Attribution Windows
Attribution windows define how long after a touchpoint a purchase can be attributed to that campaign.
Navigate to **Settings → Attribution Windows** in your Masivo dashboard.
Configure windows for each attribution type:
```json theme={null}
{
"marketing_automation_links": 10080,
"marketing_automation_emails": 2880,
"marketing_automation_push": 1440,
"loyalty": 43200,
"paid": 10080
}
```
The `paid` window applies to cross-network click IDs (`gclid`, `gbraid`,
`wbraid`, `fbclid`, `ttclid`). Masivo uses **last-touch** among owned
(email/push/link/loyalty) and paid touches that are still inside their window.
### URL-first click IDs (no cookies)
Preserve these query parameters on every redirect until purchase:
* `$tracking_id` / `tracking_id` (Masivo)
* `gclid`, `gbraid`, `wbraid` (Google)
* `fbclid` (Meta)
* `ttclid` (TikTok)
Masivo short links snapshot click IDs on `LINK_OPEN` and forward them on the
destination URL. Include the same IDs in the `attribution` object on
`PURCHASE` events so outbound CAPI/Google conversions can match.
```typescript theme={null}
const attribution = {
tracking_id: sessionTrackingId,
ad_provider: "meta",
gclid: storedGclid,
fbclid: storedFbclid
};
```
Setting windows too short may undercount attribution. Setting them too long
may overattribute to older campaigns.
### Step 2: Configure Ad Providers
Ad providers define traffic sources and their attribution share for multi-touch attribution.
Navigate to **Settings → Ad Providers** in your Masivo dashboard.
Configure providers with attribution percentages:
| Provider | Type | Attribution % | Use Case |
| - | - | - | - |
| Google Ads | `google` | 100% | Single-touch attribution |
| Meta Ads | `meta` | 50% | Multi-touch with other channels |
| Email | `email` | 100% | Direct email campaigns |
**Attribution Percentage Explained:**
* `100%`: Full revenue credit to this channel
* `<100%`: Partial credit (for multi-touch attribution)
* Used to calculate channel-specific ROI
### Step 3: Implement Attribution in Events
The most critical step is including attribution data when sending events to Masivo.
#### Essential: The `ad_provider` Parameter
The `ad_provider` is **crucial** for attribution tracking. It identifies the traffic source and must match a configured ad provider in your settings.
**Where `ad_provider` Comes From:**
1. **From Masivo Short Links** (Recommended):
Set `ad_provider` in the destination URL when creating the link:
```
https://yourapp.com/products?ad_provider=meta&utm_source=facebook&utm_campaign=summer-sale
```
When customers click the Masivo short link, they're redirected to this URL with the `ad_provider` parameter intact. Your app extracts it from the URL.
2. **Direct in Events** (Only if not from a link):
If the purchase didn't originate from a Masivo link, include it in the event:
```typescript theme={null}
const attribution = {
tracking_id: "generated-tracking-id",
ad_provider: "meta" // Only when not coming from link redirect
};
```
Always use Masivo short links for marketing campaigns - this ensures proper
ad\_provider tracking. Only include ad\_provider directly in events for non-link
traffic.
#### Complete Event Implementation
```typescript theme={null}
// When customer clicks marketing link and lands in your app
const tracking_id = extractTrackingId(window.location.href);
const ad_provider = new URLSearchParams(window.location.search).get(
"ad_provider"
);
// Store for later use
sessionStorage.setItem("masivo_tracking_id", tracking_id);
sessionStorage.setItem("masivo_ad_provider", ad_provider);
// When customer makes a purchase
const sendPurchaseEvent = async (purchaseData: {
customer_id: string;
brand_id: string;
order_id: string;
total: number;
products: any[];
}) => {
const tracking_id = sessionStorage.getItem("masivo_tracking_id");
const ad_provider = sessionStorage.getItem("masivo_ad_provider");
const eventPayload = {
type: "PURCHASE",
customer_id: purchaseData.customer_id,
brand_id: purchaseData.brand_id,
// Attribution object - critical for attribution tracking
attribution: {
tracking_id, // Links to marketing touchpoint
ad_provider, // REQUIRED - identifies traffic source
utm_source: "facebook", // Optional but recommended
utm_medium: "social", // Optional but recommended
utm_campaign: "summer-sale", // Optional but recommended
revenue: purchaseData.total, // Enables ROI calculations
cost: 5.5 // Optional: campaign cost per conversion
},
order: {
purchase_id: purchaseData.order_id,
value: purchaseData.total,
products: purchaseData.products.map(p => ({
sku: p.sku,
amount: p.quantity,
value: p.price
})),
payment_method: "CREDIT"
}
};
const response = await fetch(
"https://app.masivo.ai/api/storefront/v1/behavior/events",
{
method: "POST",
headers: {
Authorization: `Bearer ${accessToken}`,
"Content-Type": "application/json"
},
body: JSON.stringify(eventPayload)
}
);
return response.json();
};
```
## Attribution Object Structure
### Complete Attribution Schema
```typescript theme={null}
interface AttributionData {
// Core Tracking
tracking_id?: string; // UUID linking touchpoint to purchase
// Provider Identification (CRITICAL)
ad_provider?: string; // Must match configured provider name
// UTM Parameters (Recommended)
utm_source?: string; // Traffic source: "google", "facebook", "email"
utm_medium?: string; // Medium: "cpc", "social", "email", "push"
utm_campaign?: string; // Campaign name
utm_term?: string; // Paid search keywords
utm_content?: string; // Ad variation identifier
utm_referral?: string; // Specific referral source
// Financial Tracking
revenue?: number; // Purchase value for ROI calculation
cost?: number; // Acquisition cost for this conversion
}
```
### Fallback Behavior
Masivo's attribution system uses intelligent fallbacks:
```
Priority 1: Link/Email/Push attribution data
Priority 2: Purchase event attribution data
Priority 3: Default values (ad_provider: "unknown")
```
This means if a customer clicks a link without full attribution but the purchase event includes it, attribution will still work correctly.
## Attribution Calculation Logic
### Marketing Attribution Flow
1. **Customer clicks marketing link**
* `LINK_OPEN` event created with `tracking_id`
* Timestamp recorded: `2025-01-15 12:00:00`
2. **Customer makes purchase**
* `PURCHASE` event includes same `tracking_id`
* Timestamp recorded: `2025-01-17 14:30:00`
3. **Attribution calculation**
```
Time to conversion = 2 days, 2.5 hours = 3,150 minutes
Attribution window = 10,080 minutes (7 days)
Within window? 3,150 ≤ 10,080 → YES ✓
Campaign type: "marketing_automation_links"
Revenue: $99.99
Attribution: 100%
```
4. **Attribution record created**
* Links purchase to campaign
* Records conversion metrics
* Powers analytics dashboards
### Loyalty Attribution Flow
1. **Customer earns rewards**
* `PURCHASE` event creates accumulation transaction
* `event_id` links transaction to event
* Timestamp: `2025-01-10 10:00:00`
2. **Customer makes new purchase with redemption**
* `PURCHASE` event with same customer
* Customer redeems previously earned rewards
* Timestamp: `2025-02-05 15:00:00`
3. **Attribution calculation**
```
Earliest accumulation: 2025-01-10 10:00:00
Purchase time: 2025-02-05 15:00:00
Time since accumulation = 26 days, 5 hours = 37,500 minutes
Loyalty attribution window = 43,200 minutes (30 days)
Within window? 37,500 ≤ 43,200 → YES ✓
Campaign type: "loyalty"
Revenue: $149.99
Attribution: 100%
```
4. **Loyalty attribution record created**
* Tracks loyalty program impact
* Measures program ROI
* Identifies high-value loyalty customers
### Time-to-Conversion Metrics
Attribution automatically calculates and stores:
* **Marketing Campaigns**: Minutes from link click to purchase
* **Loyalty**: Minutes from first reward accumulation to purchase
* **Conversion Rate**: Percentage of clicks that convert
* **Revenue per Click**: Average revenue per campaign click
## Testing Attribution
### 1. Test Link Attribution
```bash theme={null}
# Create a test link with attribution
curl -X POST https://app.masivo.ai/api/storefront/v1/links \
-H "Authorization: Bearer YOUR_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"destination_url": "https://yourapp.com/products?ad_provider=meta",
"attribution": {
"ad_provider": "meta",
"utm_source": "facebook",
"utm_campaign": "test-campaign"
}
}'
# Click the generated short link
# Extract tracking_id from redirected URL
# Make a test purchase with that tracking_id
```
### 2. Test Loyalty Attribution
```typescript theme={null}
// 1. Create purchase that earns rewards
await sendPurchaseEvent({
type: "PURCHASE",
customer_id: "test-customer",
order: {
purchase_id: "order-1",
value: 100
}
});
// 2. Wait for reward accumulation
// 3. Create second purchase with redemption
await sendPurchaseEvent({
type: "PURCHASE",
customer_id: "test-customer",
order: {
purchase_id: "order-2",
value: 150
}
// System automatically links via event_id
});
// Check attribution records to verify loyalty attribution
```
## Best Practices
### 1. Always Include `ad_provider`
```typescript theme={null}
// ✅ GOOD - Clear provider identification
{
ad_provider: "meta",
tracking_id: "...",
utm_source: "facebook"
}
// ❌ BAD - Missing provider defaults to "unknown"
{
tracking_id: "...",
utm_source: "facebook"
}
```
### 2. Use Consistent Naming Conventions
```typescript theme={null}
// Provider names must match exactly
// Dashboard: "meta"
// Code: "meta" ✓
// Dashboard: "Google Ads"
// Code: "google_ads" ✗ → Won't match!
```
## Attribution cron (scheduler + worker)
Purchase attribution runs as two background jobs, similar to other Masivo
crons that use PGMQ workers:
| Job | Endpoint | Schedule (recommended) |
| - | - | - |
| Scheduler | `POST /api/cron/attribution/calculate_purchases` | Every 5 minutes |
| Worker | `POST /api/cron/attribution/calculate_purchases_worker` | Every 1 minute |
### How it works
1. The **scheduler** enqueues one job per account with attribution config
(default for full runs). It returns immediately and does not process purchases
inline.
2. The **worker** pulls one account per run from the
`attribution_calculate_purchases` queue, calculates attribution, and writes
to Tinybird.
3. `last_sync` advances only after the queue is empty for that run.
### Queue setup (once per environment)
If the queue does not exist yet in Supabase:
```sql theme={null}
SELECT pgmq.create('attribution_calculate_purchases');
```
Or apply `apps/next/supabase/schemas/extensions/pgmq.schema.sql`, which creates
the queue idempotently.
### Local testing
Enqueue all accounts:
```bash theme={null}
curl -X POST http://localhost:3000/api/cron/attribution/calculate_purchases \
-H "Content-Type: application/json" \
-H "checksum: $CRON_AUTH_SECRET" \
-d '{"days_back": 1, "debug": true}'
```
Drain the queue (repeat until empty):
```bash theme={null}
curl -X POST http://localhost:3000/api/cron/attribution/calculate_purchases_worker \
-H "Content-Type: application/json" \
-H "checksum: $CRON_AUTH_SECRET" \
-d '{}'
```
Process a single account inline (debug, no queue):
```bash theme={null}
curl -X POST http://localhost:3000/api/cron/attribution/calculate_purchases \
-H "Content-Type: application/json" \
-H "checksum: $CRON_AUTH_SECRET" \
-d '{"account_id": "YOUR_ACCOUNT_ID", "days_back": 1, "debug": true}'
```
Use `enqueue: false` only when you need a synchronous full run (may timeout on
large accounts).
### pg\_cron setup (Supabase)
Cron jobs live in the `cron.job` table, not in this repo. Run in the target
environment's SQL editor, replacing `` and ``:
```sql theme={null}
-- Optional: remove the legacy single-job cron if it exists
select cron.unschedule(jobid)
from cron.job
where command ilike '%/api/cron/attribution/calculate_purchases%'
and command not ilike '%calculate_purchases_worker%';
select cron.schedule(
'attribution_calculate_purchases_enqueue',
'*/5 * * * *',
$$
select net.http_post(
url := '/api/cron/attribution/calculate_purchases',
headers := '{"Content-Type":"application/json","checksum":""}'::jsonb,
body := '{}'::jsonb
);
$$
);
select cron.schedule(
'attribution_calculate_purchases_worker',
'* * * * *',
$$
select net.http_post(
url := '/api/cron/attribution/calculate_purchases_worker',
headers := '{"Content-Type":"application/json","checksum":""}'::jsonb,
body := '{}'::jsonb
);
$$
);
```
The scheduler enqueues one job per account (default). The worker drains the
`attribution_calculate_purchases` queue one account per minute. Failed jobs
retry up to 3 times (tracked in the job payload), then move to
`dlq_integrations`. `last_sync` only advances when the main queue is fully
drained, so poison accounts no longer block other accounts indefinitely.
## Troubleshooting
### No Attribution Records Created
**Problem**: Purchases not being attributed to campaigns
**Solutions**:
1. Verify `tracking_id` is included in purchase events
2. Check attribution windows aren't too restrictive
3. Confirm `ad_provider` matches configured providers
4. Ensure timestamp of purchase is after touchpoint
### Incorrect Attribution Percentage
**Problem**: Revenue is over/under-attributed
**Solutions**:
1. Review ad provider configuration percentages
2. Check for duplicate provider names
3. Verify provider names match exactly (case-sensitive)
### Missing Loyalty Attribution
**Problem**: Loyalty-driven purchases not attributed
**Solutions**:
1. Verify `event_id` exists on purchase events
2. Check loyalty attribution window is adequate (30-60 days recommended)
3. Ensure customer has reward transactions before purchase
4. Confirm reward accumulations have valid timestamps
### Time to Conversion is NULL
**Problem**: Attribution records missing conversion time
**Solutions**:
1. Check that link/accumulation timestamp is valid (not 1970-01-01)
2. Verify timestamp is before purchase time
3. Ensure `line_issued_at` or `transaction_issued_at` fields are populated
## Next steps
Once your attribution system is configured and tracking data, you can analyze performance:
* **View analytics**: Navigate to **Analytics** to see attribution reports, campaign ROI, and conversion metrics across all channels
* **Export data**: Learn how to [export analytics tables to CSV](/guides/analytics-exports) for deeper analysis of attribution patterns, multi-touch conversion paths, and campaign performance
## Related Resources
* [Attribution Tracking API Guide](/api-reference/guides/attribution-tracking) - Developer implementation details
* [Journey Triggers](/api-reference/guides/journey-triggers) - Automated campaign triggers
* [Create Links](/guides/create-links) - Marketing link creation
* [Track Customer Activity](/guides/track-customer-activity) - Event tracking basics
* [Marketing Automations](/guides/marketing-automations) - Campaign setup
## Related architecture
* [Activity & events](/resources/data-architecture#activity-events) — How purchases and touchpoints enter the attribution pipeline
* [Marketing & destinations](/resources/data-architecture#marketing-destinations) — How marketing links, journeys, and messages connect to conversions
# Redeeming rewards
Source: https://docs.masivo.ai/guides/redeeming-rewards
Learn how customers can redeem rewards
## How customers can redeem rewards
In Masivo, customers can redeem rewards on any event. For example, when a customer makes a purchase, the customer can redeem a discount to reduce the order total.
## Facts about redemption
* Rewards are only redeemed **when an event** that use them **fulfills**.
* Rewards specified to be redeemed are automatically marked as **reserved** and cannot be used until either the event is **fulfilled** or the **reservation expired**.
* It is the **responsibility of your e-commerce service** to validate the order **total to be charged** to the customer. Masivo only manages the stock of rewards.
* A reward with a global tier scope can only be redeemed by customers whose
current tier is included. Masivo reads this tier from the customer profile.
* Wallet rewards are snapshots. A reward definition update changes existing
wallet copies only when the wallet update flow is used.
If the redemption exceeds the 24-hour cap, Masivo rejects the whole request
and deducts nothing. The response is `422` with `error` set to `Internal
validation failed` and `details` set to `Cannot redeem 50 points. Only 30
left under the 24-hour redemption limit.` The event is stored with status
`FAILED`.
## Test rewards redemption
Currently, the only way to test rewards redemptions is by using our [REST
API](/api-reference). In the future, we will provide a playground to test
reward redemptions.
## Next steps
Want to analyze reward redemption patterns?
* **View analytics**: Navigate to **Analytics > Rewards** to see redemption rates, most popular rewards, and customer behavior
* **Export data**: Learn how to [export analytics tables to CSV](/guides/analytics-exports) for deeper analysis of redemption trends
# Score Streaks campaigns
Source: https://docs.masivo.ai/guides/score-streaks
Step-by-step guide to creating and integrating a score streak campaign.
Score Streaks reward customers who consistently send a qualifying event within a defined time window. This guide walks you through creating a campaign, reading streak state from the API, and handling rewards in your frontend.
## Prerequisites
* An active Masivo account with a SERVER API key
* A customer registered in Masivo with an `external_id` matching your system's customer identifier
* At least one reward configured in your account
## Create the campaign
In the dashboard navigate to **Campaigns → Score Streaks** and click **New campaign**.
Choose the event type your customers will send (e.g. `PURCHASE`, `CHECK_IN`). Every time a customer sends this event it counts toward their streak.
Set how long a customer has to send the next qualifying event before the streak is broken. For example, **1 DAY** means the customer must check in daily to keep their streak alive.
Click **Add milestone** to define score thresholds. Each milestone triggers a reward when the customer's score reaches that number in the current cycle.
Example milestone tiers:
* Score **1** → 10 points (daily reward)
* Score **7** → 100 points (weekly bonus)
* Score **30** → 500 points (monthly champion)
Toggle **Scheduled reset** if you want streaks to restart on a fixed calendar date regardless of the customer's activity — useful for monthly leaderboards or seasonal campaigns.
Set start date and publish. The campaign will start processing events immediately.
## Send qualifying events
Use the [Emit event](/api-reference/behavior/events/post) endpoint from your backend:
```bash theme={null}
POST /api/storefront/v1/behavior/events
Authorization: Bearer
x-account-id:
{
"customer_id": "user_abc123",
"brand_id": null,
"type": "PURCHASE",
"issued_at": "2026-05-21T12:00:00Z"
}
```
Masivo will:
1. Identify matching score streak campaigns for this event type
2. Find or create the customer's `score_streak` record
3. Check if the loss window expired (and close the cycle if so)
4. Increment the score and write a `GAIN` history event
5. Fire any milestone rewards and write `MILESTONE` history events
6. Return the API response — rewards accumulate in the customer's wallet
## Display streak progress
Fetch the customer's active streaks to show a live progress indicator:
```bash theme={null}
GET /api/storefront/v1/customers/{customer_id}/score-streaks
Authorization: Bearer
x-account-id:
```
Example response:
```json theme={null}
{
"data": [
{
"id": "b3e9a1f2-...",
"campaign_id": "c8d2...",
"customer_id": "user_abc123",
"status": "ACTIVE",
"current_score": 5,
"current_streak": 5,
"highest_milestone_score": 5,
"period_started_at": "2026-05-21T12:00:00Z",
"period_ends_at": "2026-05-22T12:00:00Z",
"period_score": 5,
"period_event_count": 5,
"next_reset_at": null,
"last_activity_at": "2026-05-21T12:00:00Z",
"last_loss_at": null,
"last_reset_at": null
}
]
}
```
Key fields for your UI:
| Field | Use |
| - | - |
| `current_score` | Current streak score in this cycle |
| `period_ends_at` | Deadline for next qualifying event (show a countdown) |
| `current_streak` | Total qualifying events in current cycle |
| `last_loss_at` | Last time the streak was broken |
## Show streak history
Pass `include=history` to get a flattened, time-ordered list of all history events across all streaks for this customer:
```bash theme={null}
GET /api/storefront/v1/customers/{customer_id}/score-streaks?include=history&limit=50
```
Response shape when `include=history`:
```json theme={null}
{
"data": {
"streaks": [ ... ],
"history": [
{
"id": "evt1...",
"score_streak_id": "b3e9...",
"campaign_id": "c8d2...",
"customer_id": "user_abc123",
"cycle_id": "cycl1...",
"type": "MILESTONE",
"score_delta": null,
"score_before": 4,
"score_after": 5,
"milestone_score": 5,
"period_started_at": "2026-05-21T12:00:00Z",
"period_ends_at": "2026-05-22T12:00:00Z",
"reversed_at": null,
"reason": null,
"actor_id": null,
"metadata": {},
"created_at": "2026-05-21T12:00:00Z"
}
]
}
}
```
Use `history` to render an activity feed, a streak timeline, or to debug unexpected state.
## Common patterns
### Show a countdown timer
```ts theme={null}
const streak = streaks[0];
const deadline = new Date(streak.period_ends_at);
const msLeft = deadline.getTime() - Date.now();
const hoursLeft = Math.floor(msLeft / 3_600_000);
```
### Detect a broken streak
```ts theme={null}
const wasBroken = streak.last_loss_at !== null;
const brokenAt = wasBroken ? new Date(streak.last_loss_at) : null;
```
### Find the next milestone
Given the campaign rules sorted by `milestone` ascending:
```ts theme={null}
const nextMilestone = rules
.filter(r => r.milestone > streak.current_score)
.sort((a, b) => a.milestone - b.milestone)[0];
const pointsNeeded = nextMilestone.milestone - streak.current_score;
```
## Frequently asked questions
The associated `GAIN` and `MILESTONE` history events are marked as reversed and an `EVENT_REVERSAL` history event is appended. If the reversed event was in the active cycle, the score and streak count are rolled back. The operation is idempotent.
The cron job (`/api/cron/loyalty/score_streaks`) runs periodically. When it finds a streak whose `period_ends_at` is in the past, it closes the active cycle with a `LOSS` history event. The next qualifying event from the customer starts a fresh cycle.
Yes — once per cycle. If the streak is lost (new cycle) and the customer reaches the same milestone again, the reward is granted again. Within a single cycle, each milestone fires exactly once.
The **loss condition** is triggered by inactivity: the customer fails to act before the period expires. A **scheduled reset** fires on a fixed calendar schedule regardless of activity — even a customer with a perfect streak is reset on the configured date.
Use the internal admin endpoint `POST /api/admin/score-streaks/reset` with the `account_id`, `campaign_id`, `customer_id`, and a `reason`. This requires the `checksum` header with the `CRON_AUTH_SECRET` value and is intended for support operations.
# Email settings
Source: https://docs.masivo.ai/guides/settings/email
Configure email domains, Google Postmaster, footer templates, and tracking
## What you'll learn
By the end of this guide, you'll know how to configure email settings in
Masivo, including domain verification, Google Postmaster, footer templates, and tracking controls.
## Step 1: Open email settings
Navigate to **Marketing Automation > Settings > Email**.
This tab controls domain infrastructure and default email template behavior.
## Step 2: Configure email domains
In **Email Domains**, add and manage up to **3 domains**.
Flow summary:
* Add a domain and save the form.
* Masivo registers the domain with SES.
* Configure shown DNS records in your DNS provider.
* Use **Verify** to update domain status.
Common states:
* `not_started`
* `pending`
* `verified`
* `failed`
* `temporary_failure`
The DNS table includes required SPF and DKIM records, plus a suggested DMARC
record (`_dmarc`) to improve deliverability posture.
## Step 3: Connect Google Postmaster
Google Postmaster reports how Gmail evaluates each sending domain. Before
connecting it:
* Configure the same domain under **Email Domains** in Masivo.
* Use a Google account that owns or can manage the sending domain.
In Masivo's **Google Postmaster** section, click **Connect Google** and
authorize access. Masivo stores the encrypted connection in the account's email
settings and only displays Postmaster domains that exactly match configured
sending domains.
If Google reports a domain as unverified, click **Get DNS record**, add the TXT
record to the domain's DNS configuration, then click **Verify**. Masivo registers
the domain in Postmaster when needed and checks Google's verification status
from the same screen.
After connecting, open **Analytics > Emails > Deliverability** to review:
* Gmail sender compliance and deliverability verdicts
* Spam, SPF, DKIM, DMARC, and TLS rates
* Delivery errors and temporary failures
* Feedback loop IDs and spam rates
Postmaster data covers personal Gmail accounts, is not real-time, and may be
absent when a domain is below Google's traffic or privacy thresholds.
## Step 4: Configure footer template
In **Footer**, choose your default footer template type:
* **Drag and drop**
* **HTML**
Use the footer editor to create or update your footer template. The resulting
schema/HTML is saved as account-level settings for email templates.
## Step 5: Configure email tracking
In **Email Tracking**, configure **Tracking open**.
The UI marks open tracking as "not recommended" due to deliverability and
privacy tradeoffs.
## Step 6: Save and handle onboarding case
Click **Save** in form actions after updating settings.
Special case:
* If your account has verified domains but no available dedicated IPs, the
settings form is replaced by an onboarding flow to contact support and enable
sending capacity.
## What you've accomplished
You've configured the email infrastructure and defaults required for
compliant, branded, and trackable email campaigns.
## Next steps
* Build templates with [Create email template](/guides/emails/create-email)
* Review compliance setup in
[Unsubscribe links](/guides/emails/unsubscribe-links)
* Review Google's [email sender guidelines](https://support.google.com/mail/answer/81126)
# Links settings
Source: https://docs.masivo.ai/guides/settings/links
Configure link settings for domains, redirects, app store metadata, and QR branding
## What you'll learn
By the end of this guide, you'll know how to configure global link settings in
Masivo, including custom domains, DNS records, redirects, app store identifiers,
and QR logo branding.
## Step 1: Open links settings
Navigate to **Marketing Automation > Settings > Links**.
This tab controls account-level link behavior used by your short links and QR
flows.
## Step 2: Configure custom domains
In **Custom Domains**, add and manage up to **3 domains**.
Important behavior:
* Domain format must be valid (for example: `links.example.com`).
* Avoid protocols and paths (`https://`, `/path`).
* Use **Refresh** to re-check verification status.
* Remove domains with the **Remove** action.
* For verified domains, you can upload `assetlinks.json` and
`apple-app-site-association`.
Well-known file uploads are allowed for domains already saved in your account
configuration.
## Step 3: Add DNS records
Use the **DNS Configuration** table to copy the required records to your DNS
provider.
Masivo shows the records needed to verify ownership and enable app-link
behavior.
## Step 4: Configure app store settings
In **App Store Settings**, define:
* **iOS App Store ID** (numeric ID from Apple App Store URL)
* **Android Package Name** (package identifier from Play Store/console)
These values support deep linking and mobile redirection behavior.
## Step 5: Configure default redirects
In **Redirect**, set fallback URLs for:
* **Redirect iOS**
* **Redirect Android**
* **Redirect Web**
These defaults are applied when a link does not override redirect behavior.
## Step 6: Configure QR logo
In **QR code**, upload your brand logo for generated QR assets.
Accepted formats follow image upload constraints for settings files.
## Step 7: Save and validate
Click **Save** in form actions.
Then create or edit a link in **Marketing Automation > Links** and confirm:
* The configured domain appears in domain selector.
* Redirect defaults are applied correctly.
* QR branding reflects your uploaded logo.
## What you've accomplished
You've configured global link settings required for branded domains, reliable
redirects, and QR identity in marketing automation flows.
## Next steps
* Create campaign links with [Links guide](/guides/create-links)
* Configure push prerequisites with [Push settings](/guides/settings/push)
# Marketing automation settings
Source: https://docs.masivo.ai/guides/settings/marketing-automation
Configure Links, Email, Push, and WhatsApp settings for marketing automation in Masivo
## What you'll learn
By the end of this guide, you'll know how the **Marketing Automation >
Settings** area is organized and where to configure each channel before running
campaigns.
## Settings tabs overview
In the dashboard, go to **Marketing Automation > Settings**.
You will find four active tabs:
* **Links**
* **Email**
* **Push**
* **WhatsApp**
The **Journeys** tab appears in the UI but is currently disabled for settings
configuration.
## Configure each channel
Use these guides based on the channel you want to configure:
Configure custom domains, redirects, app store IDs, and QR branding.
Configure email domains, footer templates, and email tracking options.
Upload Firebase credentials and configure device token duration.
Configure WhatsApp credentials, webhook URL, and business logo.
## Recommended order
For most teams, this sequence works best:
1. Configure **Links settings** first (custom domains and redirects).
2. Configure **Email**, **Push**, and **WhatsApp** based on channels you use.
3. Create templates and run tests before scheduling campaigns.
## Next steps
* Configure [Links settings](/guides/settings/links)
* Configure [Email settings](/guides/settings/email)
* Configure [Push settings](/guides/settings/push)
* Configure [WhatsApp settings](/guides/settings/whatsapp)
# Push settings
Source: https://docs.masivo.ai/guides/settings/push
Configure Firebase credentials and token policies for push notifications
## What you'll learn
By the end of this guide, you'll know how to configure Push settings required
for sending notifications from Masivo.
## Step 1: Open push settings
Navigate to **Marketing Automation > Settings > Push**.
## Step 2: Confirm links prerequisite
Push settings require at least one custom domain configured in links settings.
If no custom domain exists, push configuration fields remain unavailable and
the UI prompts you to configure links first.
Configure this prerequisite in [Links settings](/guides/settings/links).
## Step 3: Upload Firebase Admin SDK
In **Firebase SDK**, upload your Firebase Admin SDK JSON file.
Requirements:
* File must be `.json`.
* The file is stored in marketing automation settings storage.
* Once configured, you can run **test credentials**.
Use **test credentials** to validate your Firebase integration before sending
campaigns.
## Step 4: Configure FCM token duration
In **Fcm Token**, set **Token Duration**.
Available values:
* `7` days
* `14` days
* `30` days
* `60` days (recommended)
* `270` days
This controls the validity period for device tokens in push delivery flows.
## Step 5: Save and validate
Click **Save** in form actions after making changes.
Then test push template sending from **Marketing Automation > Push templates**
to confirm credentials and token policies are working.
Custom sounds require a native app build. Follow the
[mobile integration guide](/api-reference/guides/integrate-push-notification#custom-notification-sounds)
before selecting a custom filename in a template.
Editing is restricted for non-admin roles in this settings form.
## What you've accomplished
You've configured the push channel foundation, including Firebase
authentication and token lifecycle policy.
## Next steps
* Create campaigns with [Push guide](/guides/create-push)
* Validate SDK behavior on physical devices before production sends
# WhatsApp settings
Source: https://docs.masivo.ai/guides/settings/whatsapp
Configure WhatsApp credentials, webhook URL, and business logo for template delivery
## What you'll learn
By the end of this guide, you'll know how to configure WhatsApp settings in
Masivo so templates can be approved, tested, and delivered correctly.
## Step 1: Open WhatsApp settings
Navigate to **Marketing Automation > Settings > WhatsApp**.
## Step 2: Configure credentials
In **WhatsApp Credentials**, complete:
* **Access Token**
* **Phone Numbers** (one or more entries with `Label` and `Phone Number ID`)
* **Business Account ID**
* **App ID**
* **Verify Token**
Field behavior:
* Phone Number IDs, Business Account ID, and App ID use numeric identifiers.
* You can add/remove phone number entries from the form.
* The webhook URL is displayed read-only with a copy button.
### How to get the Access Token
Use a **system user** token from Meta Business Manager (long-lived; suitable for
production). Official steps are in Meta’s
[WhatsApp access tokens](https://developers.facebook.com/docs/whatsapp/access-tokens)
guide.
Summary:
1. Open **[Business settings](https://business.facebook.com/settings/)**
(`business.facebook.com`), then go to **Users** and **System users**.
2. Click **Add** and create a system user (choose **Admin** or **Employee**).
Admin users get full access to WABAs in your business portfolio by default;
**Employee** users need each **WhatsApp Business Account (WABA)** assigned
with **Full control** (or partial access if you prefer a tighter scope).
3. Open the system user, use **Assign assets**, and add your **Meta Developer
app** with **Manage app** so the user ends with **Full control** of that app
(reload the page if the asset does not show immediately).
4. Assign your **WhatsApp Business Account** to the same system user with
**Full control** when required (especially for **Employee** system users).
5. In the system user panel, click **Generate token**. Select your app, set
token expiration if prompted, and include at least **`whatsapp_business_messaging`**,
**`whatsapp_business_management`**, and **`business_management`**. Generate
the token and copy it once—store it securely; you may not see it again.
### How to get the Business Account ID
**Business Account ID** is your **WhatsApp Business Account ID (WABA ID)**—the
numeric id of the WABA tied to your numbers and templates.
In Meta Business Manager:
1. Open **[Business settings](https://business.facebook.com/settings/)**
(`business.facebook.com`).
2. In the sidebar, open **Accounts** and select **WhatsApp accounts** (sometimes
labeled **WhatsApp Business accounts** or grouped under WhatsApp-related
account settings, depending on locale and UI).
3. Click the WABA you use with your app. Meta shows the **WhatsApp Business
Account ID** in the account details panel—copy that value into Masivo.
If you manage several WABAs, pick the one connected to the phone lines and Meta
app you are configuring.
### How to get the App ID
**App ID** is the numeric identifier of your **Meta app** (the one that includes
the **WhatsApp** product and that you install for the system user in Business
Manager). Use the same app across Masivo, token generation, and asset
assignment so IDs and permissions stay aligned.
Per Meta’s [App Dashboard](https://developers.facebook.com/docs/development/create-an-app/app-dashboard)
documentation:
1. Open **[My Apps](https://developers.facebook.com/apps)** and select your
app.
2. After the app loads, the **toolbar at the top of the dashboard** shows the
**App ID** (it is created with the app and cannot be changed). You can also
find it under **App settings** (or **Settings**) → **Basic**, in the app’s
summary fields.
On **My Apps**, Meta lists the **business connected to the app** when one is
linked. That business portfolio should match the **Business Manager** you use
for WABAs and system users; if the app is not connected yet, complete Meta’s
business connection / verification steps from the dashboard (for example under
**Submit for review** or **Verification**, depending on your app type) so the
app belongs to the correct portfolio.
### How to get Phone Number IDs
Each row under **Phone Numbers** needs the **Phone Number ID** (Graph API id for
that line), not the E.164 phone string. Meta documents this in
[Manage phone numbers](https://developers.facebook.com/docs/whatsapp/business-management-api/manage-phone-numbers/)
(**Get all phone numbers**).
Call the Graph API with your **WhatsApp Business Account ID** (same value as
**Business Account ID** in this form) and a token that has WhatsApp management
access:
```http theme={null}
GET https://graph.facebook.com/v24.0//phone_numbers?access_token=
```
Example (replace IDs and token with yours):
`https://graph.facebook.com/v24.0/109929475293471/phone_numbers?access_token=...`
Use the **Graph API version** your app is pinned to if it differs from `v24.0`.
The JSON response includes a `data` array. For each number, copy **`id`** into
Masivo as **Phone Number ID** and use **`display_phone_number`** or
**`verified_name`** to pick the right line when labeling. Optional fields such
as `quality_rating` help you tell numbers apart.
### Configure the webhook in Meta
Meta delivers WhatsApp events to the **callback URL** you register and checks
your **Verify Token** during setup. Use the **same** callback URL and verify
token as in Masivo (**Verify Token** in this form and the read-only webhook
URL). See Meta’s
[Set up webhooks](https://developers.facebook.com/docs/whatsapp/cloud-api/guides/set-up-webhooks/)
guide for field subscriptions and behavior.
In **[Meta App Dashboard](https://developers.facebook.com/apps)**, open your app
(the one with WhatsApp). If the app was created with the **Connect with
customers through WhatsApp** use case, go to:
1. **Use cases**
2. **Connect with customers through WhatsApp**
3. **Customize**
4. **Configuration**
There, set the **Callback URL** to Masivo’s webhook URL, enter the **Verify
token** to match Masivo exactly, and subscribe to the **webhook fields** you
need (for example **messages** and template or account fields for delivery and
status updates).
If your dashboard uses the classic layout instead, Meta documents the same panel
as **WhatsApp** → **Configuration** in the App Dashboard.
## Step 3: Configure business logo
In **Business Logo**, upload the WhatsApp profile image used in chat previews.
This file is stored in marketing automation settings storage for your account.
## Step 4: Save and understand masking behavior
Click **Save** in form actions.
After saving:
* Sensitive credentials are masked in the UI.
* Masked fields become disabled/read-only.
* Save actions are blocked while masked values are present.
Configure and verify all required fields before saving to avoid incomplete
credential state.
Editing is restricted for non-admin roles in this settings form.
## What you've accomplished
You've configured WhatsApp channel credentials, webhook connectivity, and
branding needed for template operations in Masivo.
## Next steps
* Create and manage templates in [WhatsApp templates guide](/guides/create-whatsapp)
* Use approved templates in [Journeys](/guides/create-journeys)
# Setup a Tier System
Source: https://docs.masivo.ai/guides/setup-a-tier-system
Learn how to set up a tiers system in Masivo
## Tiers in Masivo
To create a tier system in Masivo, first navigate to the **Tiers** section in the dashboard. you can find them in the left sidebar.
Let's imagine we want to create a tier system with three levels: **Bronze**, **Silver**, and **Gold**.
When you first create an account, a default tier called **Base** is created. This tier is the one that all customers will be assigned to by default.
This base tier cannot be deleted, but you can edit it to change its name.
## Updating an existing tier
In the table of tiers, click the options icon and then click **Edit item**.
Now, in the **Base Setting** section, you can change the name of the base tier to **Bronze**.
Then click **Save** at the bottom of the page.
## Creating new tiers
To create a new tier, click the **New tier** button.
You will notice the **Tier level** field is set to **2** since we already have one tier.
Give the new tier the name **Silver**.
## Tier conditions
Now, since this is a premium tier, we want to set some conditions for customers to reach this tier.
In the **Entry conditions** section, we can set the minimum **Customer number of purchases** required to reach this tier.
In this example we will set it to **greater than 3**. Click Save.
Now click **Save** again and repeat the process to create the **Gold** tier. We will set the **Customer number of purchases** to **greater than 5**.
That's it! You have created a tier system in Masivo.
## Exit conditions
You can also set conditions for customers to exit a tier.
For example, we can edit the **Silver** tier and set the **Customer number of purchases** to **less than or equal to 3**. In the **Exit conditions** section.
Do the same for the **Gold** tier, setting the **Customer number of purchases** to **less than or equal to 5**.
## Automatic tier change
Now customers will be automatically moved to the next tier when they meet the entry conditions.
Customers will be moved back to the previous tier when they meet the exit conditions **At the start** of your next **Reporting period**.
You can update your reporting period in your **Account Settings**
Additionally, you can give customers a **grace period** to keep their tier for an additional period. Set it in the **Tier settings** tab in the tiers section.
## Related architecture
* [Registration flow](/resources/data-architecture#registration-flow) — Initial tier assignment when a customer registers
* [Activity & events](/resources/data-architecture#activity-events) — How events and metrics drive tier upgrades and downgrades
# Template variants
Source: https://docs.masivo.ai/guides/template-variants
A/B test email, push, WhatsApp, in-app, and News Feed templates in Masivo
Template variants let you test another title, text, or design on the same
template without changing shared settings such as alias, description, or
brand.
Template variants are **not** the same as [journey split variants](/guides/create-journeys#split-variants-ab-paths). Splits send customers down different paths. Template variants change the message copy or layout that a send uses.
## What you'll learn
By the end of this guide, you'll know how to add up to four variants on a
template, how scheduled sends split traffic, and how journeys pick a variant.
## Where variants are available
You can add variants on:
* [Email templates](/guides/emails/create-email)
* [Push templates](/guides/create-push)
* [WhatsApp templates](/guides/create-whatsapp)
* [In-app templates](/guides/create-in-app-templates)
* [News Feed templates](/guides/news-feed)
Each channel uses the same **A/B testing** section in the template editor.
## Add and manage variants
Open a template in **Marketing Automation**. The **A/B testing** section lists
the current versions and shows a `1/4 variants` badge (up to `4/4`).
A new template starts with a single version. Empty-state copy explains when to
add another, for example a different subject (email), title (push), layout
(in-app), or card design (News Feed).
### Create a variant
* **New Variant Template** creates an empty version.
* The chevron next to that button clones an existing variant. Search by label
in **Search variant...**.
You can rename each row. If you leave the label empty, Masivo uses
`Variant A`, `Variant B`, `Variant C`, or `Variant D`.
Select a row to edit that version. Content fields are marked **Per Variant**.
You cannot remove the last remaining variant.
Shared settings stay on the template: alias, description, brand, and (for
WhatsApp) the sending phone number.
## What each variant can change
| Channel | Per-variant content |
| - | - |
| Email | Sender name, subject, preheader, and email design |
| Push | Title, body, image, sound, and deep link |
| WhatsApp | Header, body, footer, buttons, and a Meta template ID |
| In-app | Message type, title, body, image, buttons, display rules, and custom-code options |
| News Feed | Card type, title, message, image, link, pinned state, priority, and expiration |
### WhatsApp approval
WhatsApp variants each have their own Meta template ID. On save, Masivo
submits each variant to Meta for approval in one step. Each variant has its
own status (`PENDING`, `APPROVED`, `REJECTED`). A variant must be approved
before you can send it in a test, schedule, or journey.
## Test sends
Use **Send test** on the template.
* **Specific customers**: choose **Send variant** to force one version.
* **Test group / audience**: set **Variant distribution**. Weights are whole
numbers and must sum to `100%`. Use **Distribute evenly** to split the
remainder automatically (for example two variants become `50` / `50`).
If the weights do not sum to `100%`, Masivo shows
`The sum of the variants must be 100%.` and the send does not run.
## Scheduled audience sends
When you **Schedule** a template that has more than one variant, the same
**Variant distribution** control appears. Weights must sum to `100%` before
you can create the scheduled notification.
Masivo assigns each recipient a variant using those weights (probabilistic). A
weight of `0` means that variant is not selected for that send.
A template with a single version does not show distribution controls.
## Journeys
Journey actions do **not** use weight distribution. If the selected template
has more than one variant, the action form shows **Select a variant** and you
must pick one before you can save. That action always sends the chosen
version.
See [Journeys](/guides/create-journeys#template-variants-in-journey-actions)
for the action fields (`Email variant`, `Push variant`, `In-app variant`,
`WhatsApp variant`, `News feed variant`).
## Analytics
When a template has more than one variant, the template page in **Analytics**
includes a variants table (alias, sends, and channel engagement metrics). You
can download a variants report from that table.
Open the template under **Analytics > Emails**, **Push**, **WhatsApp**,
**In-app**, or **News Feed**.
## Next steps
* Personalize copy with [template variables](/guides/using-template-variables)
* Use a variant in an automated step: [Journeys](/guides/create-journeys)
* Export tables to CSV: [Analytics Exports](/guides/analytics-exports)
# Ticket Journey Triggers
Source: https://docs.masivo.ai/guides/ticket-journey-triggers
Configure journeys triggered by ticket lifecycle events
## Overview
Tickets emit domain events that are published as behavior events (`TICKET_*`). Journeys can listen to these events on edge triggers, the same way as custom events.
Ticket journeys use the ticket as their execution subject. Two tickets associated
with the same customer enter and progress independently, and a ticket without a
customer can still complete ticket-safe steps.
## Protected event types
Ticket lifecycle events are internal protected behavior events. They can trigger journeys, but they cannot be emitted from Storefront event endpoints or created as custom event types:
* `TICKET_CREATED`
* `TICKET_UPDATED`
* `TICKET_PRIORITY_CHANGED`
* `TICKET_STAGE_CHANGED`
* `TICKET_ASSIGNEE_ADDED`
* `TICKET_ASSIGNEE_REMOVED`
* `TICKET_RESOLVED`
* `TICKET_CLOSED`
* `TICKET_REOPENED`
* `TICKET_COMMENT_ADDED`
## Recommended pilot journey
1. Select **Ticket** as the journey subject.
2. Select the ticket pipeline this journey manages.
3. Use `TICKET_CREATED` as the entry trigger.
4. Add ticket-safe actions: email to an explicit recipient, change ticket stage, compensate customer, or loop.
5. Add a **New ticket stage** condition to `TICKET_STAGE_CHANGED`, for example **New ticket stage is Waiting Customer**.
6. Use **Change ticket stage** to move the current ticket to an exact stage from the selected pipeline.
Actions that require a customer profile, such as push, in-app, WhatsApp,
destination, Braze, and Meta actions, are not available in ticket journeys.
Ticket email requires an explicit recipient address and does not depend on a
customer association.
## Ticket stage action
The stage action always targets the ticket that owns the journey execution. It
does not update every ticket associated with the same customer or source journey.
The pipeline is selected once in the journey settings. Every **Change ticket
stage** action then offers only active stages from that pipeline. The journey
stores its pipeline id and each action stores a stable stage code, so renaming
a stage does not break the flow. Changing the journey pipeline resets its stage
actions to the new pipeline's default stage.
## Ticket conditions
When a `TICKET_*` trigger is selected, the condition editor only exposes
the ticket fields available in that event. Order, purchase, RFM, tier, and
unrelated customer conditions are hidden because they cannot be evaluated from
a ticket lifecycle event.
`TICKET_CREATED` can branch using the ticket data available at creation:
* title and description
* priority, status, pipeline, stage, and source
* customer, assignee, and review presence or identifiers
* brand, channel, and store identifiers
Lifecycle-specific events add their transition data:
| Event | Additional ticket properties |
| - | - |
| `TICKET_UPDATED` | changed field |
| `TICKET_PRIORITY_CHANGED` | previous and new priority |
| `TICKET_STAGE_CHANGED` | previous/new pipeline and stage, plus stage type |
| `TICKET_RESOLVED`, `TICKET_CLOSED`, `TICKET_REOPENED` | stage transition properties |
| `TICKET_COMMENT_ADDED` | comment ID and internal-comment flag |
Priority, status, and source use predefined values. Pipeline and stage values
are loaded from the account, so branches do not require internal codes.
## Related entity assignees
Ticket journey templates can use the responsible people assigned to the
ticket's store, brand, campaigns, and journeys. Select the entity in the
variable picker, then use one of these fields:
| Field | Value |
| - | - |
| `assignees` | Names, with email or user ID as fallback |
| `assignee_emails` | Email addresses; valid as an email action recipient |
| `assigned_user_ids` | User IDs |
For example, `{{store.assignees}}` renders the store's responsible people and
`{{brand.assignee_emails}}` can be used as the recipient of a ticket email.
Each address receives its own email. If nobody is assigned, that send is
skipped. The recipient field only accepts email variables and warns when the
chosen variable is not an email.
Campaign and journey variables combine the responsible people from every
matching association on the ticket and remove duplicates.
Only active profiles that still belong to the ticket's account are included.
Values are captured when the ticket event is emitted, so a journey execution
uses the assignments stored in that event.
## Event delivery
Ticket lifecycle changes publish protected behavior events (`TICKET_*`) directly to the `events` table and enqueue them for journeys. No separate outbox table or custom event type rows are required.
The journey queue does not derive routing from a ticket's existing journey
association. Every active ticket journey with a matching trigger can evaluate
the event when the ticket belongs to that journey's configured pipeline.
## Compensate a customer
Use **Compensate customer** after a ticket condition that qualifies for service
recovery, such as an urgent ticket or a specific resolution stage. Configure the
reward and policy in the action dialog. This action is available in ticket and
customer journeys. Ticket journeys use the customer currently associated with
the ticket. Customer journeys use the customer executing the step, without
requiring a ticket.
For example, branch on an urgent ticket, issue one discount valid for 30 days,
and then continue to the next ticket stage. Keep the default account scope and
720-hour cooldown to prevent a second ticket from issuing another compensation
within 30 days. Select **Only once, ever** to block future compensations entirely.
| Setting | Behavior |
| - | - |
| Reward | Active reward from the account; a brand-specific reward must match the ticket brand or customer journey event brand |
| Amount | Positive amount, up to 1,000,000; decimals are supported only for points |
| Valid for | 1–3,650 days from issuance |
| Reason | Required description saved on the accumulation transaction |
| Count previous compensations | All customer and ticket journeys in the account, this journey, or this reward across journeys |
| Repeat | Once ever, after a cooldown of 1–87,600 hours, or for each eligible execution |
| Lifetime maximum | Optional cap of 1–10,000 successful compensations per customer |
| Rolling maximum | Optional cap of 1–1,000 compensations in the preceding 1–3,650 days |
| Skip if reward available | Skip when the wallet contains a positive, unexpired amount of the selected reward, including reserved rewards and rewards from campaigns |
Defaults: one reward, 30-day validity, account-wide 30-day cooldown, no additional
caps, and skip if the reward is already available. Caps count issuance operations,
not reward units. All configured limits apply together. They count successful
customer and ticket journey compensations; other campaigns do not consume these caps. Changing a
policy applies its new settings to the existing issuance history.
A ticket receives at most one compensation across all journeys, including after
reopening, loops, retries or a change in its customer association. Tickets with
no customer skip this action and continue. Reaching a customer limit also skips
the action without delaying or closing the ticket. Skipped actions are not
scheduled for another attempt when their cooldown ends.
Customer journeys deduplicate each action within a step. Retrying the same step
does not issue another reward; a new entry can issue one if the policy allows it.
The scheduled executor processes nodes containing compensation, including nodes
that also contain in-app messages.
The wallet and its normal `ACCUMULATION` transaction are committed together with
a durable `ticket_compensations` record. Redeeming or expiring the reward does not
reset the history. The transaction records `TICKET_COMPENSATION` or
`JOURNEY_COMPENSATION` as its event type and includes `ticket_id`, `journey_id`,
`node_id`, `step_id` and `action_index` in metadata. No campaign is
created. Gift cards use the existing code inventory; insufficient stock fails
atomically without issuing a reward or consuming a customer limit.
Missing or invalid rewards, ambiguous customer associations, and database errors
are execution failures, not policy skips. A failed commit leaves the action
available for the executor to retry. Concurrent executions serialize decisions
for the same account/customer, and wallet version conflicts reload the wallet
before retrying. This protects balances modified by other loyalty operations.
### Applying the database change
After the original ticket compensation migration, apply
`20260929160000_extend_journey_compensation.sql`. This incremental migration
preserves existing grants and adds the service-role-only
`commit_journey_compensation` RPC. It retains the previous RPC during deployment.
Then run `pnpm gen:db-types:local` or `pnpm gen:db-types` against the updated
database. The ledger is operational history; reward reporting continues through
normal transactions.
# Ticket management
Source: https://docs.masivo.ai/guides/ticket-management
Complete guide to managing tickets, assignees, stages, and multi-channel communication threads via the dashboard and Storefront API.
This guide is written for **support leads, CRM operators, and integrators** who need to run tickets correctly in Masivo — including when conversations happen on **email, WhatsApp, chat, and reviews** at the same time.
If terms like *thread* or *external\_message\_id* feel unfamiliar, read [Tickets & communication (concepts)](/concepts/tickets) first, then come back here for practical steps.
## Before you start
You need a **SERVER API key** and your **account id** (`x-account-id`). Create keys in the dashboard under **Settings → API keys**. Never expose SERVER keys in browser code.
All examples use:
```
https://app.masivo.ai/api/storefront/v1
```
Every request also requires:
```http theme={null}
Authorization: Bearer
x-account-id:
Content-Type: application/json
```
* **Dashboard only:** use CRM → Tickets for day-to-day work.
* **Integration:** your mail/helpdesk connector creates threads + messages via API; agents work in Masivo or your UI.
* **Hybrid:** reviews create tickets automatically; agents reply via API or dashboard.
***
## Quick reference — all ticket & communication endpoints
| Method | Path | What it does |
| - | - | - |
| `GET` | `/ticket-pipelines` | List pipelines and ordered stages, including their internal state |
| `POST` | `/tickets` | Create a ticket and optionally route it to an available agent |
| `GET` | `/tickets/{id}` | Get one ticket **including** `communication_summary` and assignees |
| `PATCH` | `/tickets/{id}` | Update title, priority, stage, assignees in one call |
| `GET` | `/tickets/{id}/assignees` | List active assignee records |
| `POST` | `/tickets/{id}/assignees` | Add one assignee or watcher |
| `DELETE` | `/tickets/{id}/assignees` | Remove an assignee |
| `GET` | `/communication/threads` | List thread **shells** (summaries, no message bodies) |
| `POST` | `/communication/threads` | Attach a new thread to a ticket (or review/customer) |
| `GET` | `/communication/threads/{id}` | Get one thread shell |
| `GET` | `/communication/threads/{id}/messages` | List messages in chronological order |
| `POST` | `/communication/threads/{id}/messages` | Add inbound, outbound, or internal message |
API reference pages with schemas: [Tickets group](/api-reference/tickets/post) in the sidebar.
***
## Part 1 — Creating tickets
### 1A. Find the pipeline and stage ids
```bash theme={null}
curl "https://app.masivo.ai/api/storefront/v1/ticket-pipelines" \
-H "Authorization: Bearer $SERVER_API_KEY" \
-H "x-account-id: $ACCOUNT_ID"
```
Use the returned pipeline `id` and stage `code`, and send both when creating a ticket.
Pipeline administration is dashboard-only under **CRM → Pipelines**. The
Storefront API intentionally exposes discovery through `GET /ticket-pipelines`
but does not expose create, update, or delete pipeline endpoints.
### 1B. Create a manual ticket
Use when an agent or your system opens a case without a linked review.
```bash theme={null}
curl -X POST "https://app.masivo.ai/api/storefront/v1/tickets" \
-H "Authorization: Bearer $SERVER_API_KEY" \
-H "x-account-id: $ACCOUNT_ID" \
-H "Content-Type: application/json" \
-d '{
"title": "Order #8821 arrived damaged",
"description": "Customer sent photos of broken packaging.",
"pipeline_id": "770e8400-e29b-41d4-a716-446655440002",
"stage_code": "new",
"priority": "high",
"source_type": "manual",
"customer_id": "cust_external_123",
"assignee_user_ids": ["550e8400-e29b-41d4-a716-446655440000"],
"idempotency_key": "create-ticket-order-8821-v1"
}'
```
| Field | Required? | Notes |
| - | - | - |
| `title` | Yes | Max 200 characters |
| `description` | No | Max 5000 characters |
| `pipeline_id` | No | Pipeline UUID from `GET /ticket-pipelines` |
| `stage_code` | No | Internal stage code in the selected pipeline |
| `priority` | No | `low`, `medium`, `high`, `urgent` — default `medium` |
| `source_type` | No | Default `manual` |
| `review_id` | No | If set, creates a **review** ticket instead (see below) |
| `customer_id` | No | External customer id; stored as a `customer` association |
| `assignee_user_ids` | No | Array of team member UUIDs |
| `auto_assign` | No | Defaults to `true`; set `false` to keep an unassigned ticket out of routing |
| `actor_user_id` | No | Who performed the action; defaults to the first account member |
| `idempotency_key` | No | **Strongly recommended.** Re-posting the same key returns the original ticket |
| `required_fields` | No | Required when creating directly in `resolved` / `closed` stage |
**Tip:** Always send an `idempotency_key` from your integration so network retries do not create duplicate tickets.
### 1C. Create a ticket from a review
When `review_id` is present, Masivo:
1. Creates a ticket with `source_type: review`
2. Deduplicates — only **one open ticket per review**
3. Links review, customer, and journey associations when available
4. Creates a **review communication thread** with the review answers as the first inbound message
```bash theme={null}
curl -X POST "https://app.masivo.ai/api/storefront/v1/tickets" \
-H "Authorization: Bearer $SERVER_API_KEY" \
-H "x-account-id: $ACCOUNT_ID" \
-H "Content-Type: application/json" \
-d '{
"review_id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"title": "Negative review — delivery issue",
"priority": "urgent",
"idempotency_key": "review-ticket-a1b2c3d4"
}'
```
After creation, open the ticket in the dashboard **Conversation** tab — you should see a `review` channel thread with the submission text.
***
## Part 2 — Reading and updating tickets
### 2A. Get a ticket (with communication rollup)
```bash theme={null}
curl "https://app.masivo.ai/api/storefront/v1/tickets/TICKET_UUID" \
-H "Authorization: Bearer $SERVER_API_KEY" \
-H "x-account-id: $ACCOUNT_ID"
```
Example response (abbreviated — important fields only):
```json theme={null}
{
"data": {
"id": "f47ac10b-58cc-4372-a567-0e02b2c3d479",
"account_id": "550e8400-e29b-41d4-a716-446655440000",
"pipeline_id": "770e8400-e29b-41d4-a716-446655440002",
"stage_code": "in_progress",
"status": "open",
"priority": "high",
"title": "Order #8821 arrived damaged",
"description": "Customer sent photos of broken packaging.",
"source_type": "manual",
"review_id": null,
"brand_id": "brand_01",
"channel_id": "web",
"store_id": "store_nyc",
"assignees": [
{
"user_id": "660e8400-e29b-41d4-a716-446655440001",
"role": "assignee",
"assigned_at": "2026-06-25T14:00:00.000Z",
"assigned_by_user_id": "550e8400-e29b-41d4-a716-446655440000"
}
],
"communication_summary": {
"thread_count": 2,
"latest_thread_id": "8b3b9f2a-1c2d-4e5f-9a0b-1c2d3e4f5a6b",
"latest_message_id": "9c4c0a3b-2d3e-4f60-0b1c-2d3e4f5a6b7c",
"latest_message_at": "2026-06-25T16:30:00.000Z",
"latest_message_preview": "Thanks, replacement is on the way.",
"channel": "whatsapp",
"channel_account_id": "+5491112345678",
"external_thread_id": "wa-thread-8821",
"direction": "outbound",
"unread_count": 3,
"status": "open"
},
"associations": [
{
"associated_type": "customer",
"associated_id": "cust_external_123",
"created_at": "2026-06-25T14:00:00.000Z"
}
],
"stage_history": [],
"metadata": {},
"resolution_summary": null,
"closure_reason": null,
"created_at": "2026-06-25T14:00:00.000Z",
"updated_at": "2026-06-25T16:30:00.000Z"
}
}
```
Use `communication_summary` on list/board views. Load full threads only when the user opens the conversation.
### 2B. Update title, description, or priority
```bash theme={null}
curl -X PATCH "https://app.masivo.ai/api/storefront/v1/tickets/TICKET_UUID" \
-H "Authorization: Bearer $SERVER_API_KEY" \
-H "x-account-id: $ACCOUNT_ID" \
-H "Content-Type: application/json" \
-d '{
"title": "Order #8821 — replacement shipped",
"priority": "medium"
}'
```
### 2C. Change stage (move on the board)
```bash theme={null}
curl -X PATCH "https://app.masivo.ai/api/storefront/v1/tickets/TICKET_UUID" \
-H "Authorization: Bearer $SERVER_API_KEY" \
-H "x-account-id: $ACCOUNT_ID" \
-H "Content-Type: application/json" \
-d '{
"to_stage_code": "waiting_customer"
}'
```
Valid values are scoped to the ticket's pipeline. Read them from `GET /ticket-pipelines`; do not hard-code display names.
To move between pipelines, send both target values:
```json theme={null}
{
"to_pipeline_id": "880e8400-e29b-41d4-a716-446655440003",
"to_stage_code": "a2ac1542-934c-4b8f-b8cf-4a64ba923fb5"
}
```
**Resolving** — you must include a resolution summary:
```bash theme={null}
curl -X PATCH "https://app.masivo.ai/api/storefront/v1/tickets/TICKET_UUID" \
-H "Authorization: Bearer $SERVER_API_KEY" \
-H "x-account-id: $ACCOUNT_ID" \
-H "Content-Type: application/json" \
-d '{
"to_stage_code": "resolved",
"required_fields": {
"resolution_summary": "Replacement order shipped via WhatsApp confirmation."
}
}'
```
**Closing** — you must include a closure reason:
```bash theme={null}
curl -X PATCH "https://app.masivo.ai/api/storefront/v1/tickets/TICKET_UUID" \
-H "Authorization: Bearer $SERVER_API_KEY" \
-H "x-account-id: $ACCOUNT_ID" \
-H "Content-Type: application/json" \
-d '{
"to_stage_code": "closed",
"required_fields": {
"closure_reason": "Customer confirmed receipt. No further action."
}
}'
```
**Reopening:** move back to any open stage. Masivo records `reopened_at` when leaving a closed-type stage. An inbound customer reply also reopens the ticket automatically in the first open stage of its current pipeline.
You can combine stage changes with assignee updates in the **same** `PATCH` request.
### 2D. Manage assignees
**Option A — via PATCH (common in integrations):**
```bash theme={null}
# Add one assignee
curl -X PATCH "https://app.masivo.ai/api/storefront/v1/tickets/TICKET_UUID" \
-H "Authorization: Bearer $SERVER_API_KEY" \
-H "x-account-id: $ACCOUNT_ID" \
-H "Content-Type: application/json" \
-d '{ "add_assignee_user_id": "660e8400-e29b-41d4-a716-446655440001" }'
# Remove one assignee
curl -X PATCH "https://app.masivo.ai/api/storefront/v1/tickets/TICKET_UUID" \
-H "Authorization: Bearer $SERVER_API_KEY" \
-H "x-account-id: $ACCOUNT_ID" \
-H "Content-Type: application/json" \
-d '{ "remove_assignee_user_id": "660e8400-e29b-41d4-a716-446655440001" }'
# Replace entire assignee list
curl -X PATCH "https://app.masivo.ai/api/storefront/v1/tickets/TICKET_UUID" \
-H "Authorization: Bearer $SERVER_API_KEY" \
-H "x-account-id: $ACCOUNT_ID" \
-H "Content-Type: application/json" \
-d '{ "assignee_user_ids": ["660e8400-e29b-41d4-a716-446655440001"] }'
```
**Option B — dedicated assignee endpoints:**
```bash theme={null}
# POST /tickets/{id}/assignees — body: { "user_id": "...", "role": "assignee" }
# DELETE /tickets/{id}/assignees — body: { "user_id": "..." }
# GET /tickets/{id}/assignees — list active records
```
Re-adding an existing assignee is safe (idempotent).
***
## Part 3 — Communication threads (the conversation layer)
### Mental model
* One ticket can have **many threads** (e.g. email + WhatsApp).
* Each thread belongs to **one channel** and optionally maps to an external provider thread id.
* Messages always belong to **exactly one thread**.
```mermaid theme={null}
sequenceDiagram
participant Provider as Email / WhatsApp provider
participant API as Masivo API
participant Agent as Agent dashboard
Provider->>API: POST thread (first time)
Provider->>API: POST message (inbound)
API-->>Agent: TICKET_MESSAGE_RECEIVED event
Agent->>API: POST message (outbound reply)
API-->>Agent: TICKET_AGENT_REPLIED event
```
### 3A. List threads for a ticket
Returns summaries only — **not** message bodies.
```bash theme={null}
curl "https://app.masivo.ai/api/storefront/v1/communication/threads?ticket_id=TICKET_UUID&from=0&to=49" \
-H "Authorization: Bearer $SERVER_API_KEY" \
-H "x-account-id: $ACCOUNT_ID"
```
| Query param | Default | Purpose |
| - | - | - |
| `ticket_id` | — | Filter threads on this ticket |
| `review_id` | — | Filter by review |
| `customer_id` | — | Filter by customer external id |
| `channel` | — | `email`, `whatsapp`, `chat`, `review`, `system`, `other` |
| `status` | — | `open` or `closed` |
| `from` / `to` | `0` / `49` | Pagination (max 100 rows per request) |
Example thread shell:
```json theme={null}
{
"id": "8b3b9f2a-1c2d-4e5f-9a0b-1c2d3e4f5a6b",
"ticket_id": "f47ac10b-58cc-4372-a567-0e02b2c3d479",
"channel": "email",
"channel_account_id": "support@example.com",
"external_thread_id": "mailgun-thread-abc123",
"status": "open",
"latest_message_preview": "I still have not received a refund.",
"latest_message_at": "2026-06-25T15:00:00.000Z",
"latest_message_direction": "inbound",
"unread_count": 2,
"metadata": {}
}
```
### 3B. Create a thread on a ticket
Do this **once per external conversation** before posting messages.
```bash theme={null}
curl -X POST "https://app.masivo.ai/api/storefront/v1/communication/threads" \
-H "Authorization: Bearer $SERVER_API_KEY" \
-H "x-account-id: $ACCOUNT_ID" \
-H "Content-Type: application/json" \
-d '{
"ticket_id": "f47ac10b-58cc-4372-a567-0e02b2c3d479",
"channel": "email",
"channel_account_id": "support@example.com",
"external_thread_id": "mailgun-thread-abc123",
"metadata": { "provider": "mailgun" }
}'
```
You must provide at least one of: `ticket_id`, `review_id`, `customer_id`, or `external_thread_id`.
If you POST again with the same `(channel, channel_account_id, external_thread_id)`, Masivo returns the **existing** thread — this is intentional.
### 3C. List messages in a thread
```bash theme={null}
curl "https://app.masivo.ai/api/storefront/v1/communication/threads/THREAD_UUID/messages?from=0&to=99" \
-H "Authorization: Bearer $SERVER_API_KEY" \
-H "x-account-id: $ACCOUNT_ID"
```
Messages are ordered chronologically (`sent_at`, then `created_at`). Pagination default: rows `0–99`, max range 200.
### 3D. Post messages
#### Inbound customer email (first message in thread)
```bash theme={null}
curl -X POST "https://app.masivo.ai/api/storefront/v1/communication/threads/THREAD_UUID/messages" \
-H "Authorization: Bearer $SERVER_API_KEY" \
-H "x-account-id: $ACCOUNT_ID" \
-H "Content-Type: application/json" \
-d '{
"external_message_id": "mailgun-msg-001",
"direction": "inbound",
"sender_type": "email",
"sender_email": "maria@example.com",
"sender_display_name": "María González",
"subject": "Re: Order #8821 damaged",
"body_text": "I have been waiting three days with no answer.",
"status": "received",
"raw_payload": { "provider": "mailgun", "id": "mailgun-msg-001" }
}'
```
#### Agent outbound reply (linked to parent)
```bash theme={null}
curl -X POST "https://app.masivo.ai/api/storefront/v1/communication/threads/THREAD_UUID/messages" \
-H "Authorization: Bearer $SERVER_API_KEY" \
-H "x-account-id: $ACCOUNT_ID" \
-H "Content-Type: application/json" \
-d '{
"parent_message_id": "PARENT_MESSAGE_UUID",
"external_message_id": "mailgun-msg-002-out",
"direction": "outbound",
"message_type": "reply",
"sender_type": "agent",
"sender_user_id": "660e8400-e29b-41d4-a716-446655440001",
"body_text": "We are sorry — we are reviewing your case now.",
"status": "sent"
}'
```
If `message_type` is omitted and `parent_message_id` is set, Masivo defaults to `reply`.
#### Internal note (agents only — customer never sees this)
```bash theme={null}
curl -X POST "https://app.masivo.ai/api/storefront/v1/communication/threads/THREAD_UUID/messages" \
-H "Authorization: Bearer $SERVER_API_KEY" \
-H "x-account-id: $ACCOUNT_ID" \
-H "Content-Type: application/json" \
-d '{
"direction": "internal",
"message_type": "internal_note",
"sender_type": "agent",
"sender_user_id": "660e8400-e29b-41d4-a716-446655440001",
"body_text": "Customer is upset on email — prioritize WhatsApp.",
"external_message_id": "internal-note-001"
}'
```
Internal notes do **not** trigger `TICKET_CUSTOMER_REPLIED` or `TICKET_AGENT_REPLIED`.
### Message field cheat sheet
| Field | When to use |
| - | - |
| `direction` | **Required.** `inbound` = customer→you, `outbound` = you→customer, `internal` = team-only |
| `external_message_id` | **Required for sync.** Provider's unique message id |
| `parent_message_id` | When replying to a specific message in the **same thread** |
| `body_text` / `body_html` / `subject` | At least **one** required |
| `sender_user_id` | Agent UUID for outbound/internal |
| `sender_email` / `sender_display_name` | Customer identity for inbound email |
| `read_at` | Set on inbound when your UI marks as read (stops unread increment) |
| `raw_payload` | Store provider metadata for debugging |
### What updates automatically when you post a message
Masivo updates the parent thread:
* `latest_message_id`, `latest_message_at`, `latest_message_preview`
* `latest_message_channel`, `latest_message_direction`
* `unread_count` (+1 for inbound messages without `read_at`)
The ticket's `communication_summary` updates the next time you `GET /tickets/{id}`.
***
## Part 4 — End-to-end recipes
### Recipe A — Angry email, then resolve on WhatsApp
Real pattern from multi-channel support:
`POST /tickets` with title/description and optional `customer_id`.
`POST /communication/threads` with `channel: email` and provider ids.
For each inbound email, `POST …/messages` with unique `external_message_id`.
Second `POST /communication/threads` with `channel: whatsapp`.
Post inbound/outbound messages on the WhatsApp thread.
`PATCH /tickets/{id}` with `to_stage_code: resolved` and
`resolution_summary`.
Verify:
```bash theme={null}
# Should show thread_count: 2 and latest channel whatsapp
curl "https://app.masivo.ai/api/storefront/v1/tickets/TICKET_UUID" \
-H "Authorization: Bearer $SERVER_API_KEY" \
-H "x-account-id: $ACCOUNT_ID"
```
### Recipe B — Sync-only integration (your app owns the UI)
1. Webhook from email provider → map to `POST …/threads` (if new) + `POST …/messages`.
2. Poll `GET …/threads?ticket_id=` for unread badges.
3. When agent sends from your UI → `POST …/messages` with `direction: outbound`.
4. Stage changes → `PATCH /tickets/{id}`.
Always store Masivo's `thread.id` and `message.id` alongside provider ids in your database.
### Recipe C — Review → ticket → human follow-up
1. Customer submits review (existing review API).
2. `POST /tickets` with `review_id` — ticket + review thread created automatically.
3. Agent reads review in Conversation tab or via messages API.
4. Create additional threads (email/WhatsApp) if you contact the customer on other channels.
5. Resolve with `resolution_summary` when done.
***
## Part 5 — Working in the dashboard
| Area | What you can do |
| - | - |
| **Tickets board** | Kanban by stage; cards show communication preview and unread count |
| **Tickets table** | Search tickets and filter by parent category and subcategory |
| **CRM → Categories** | Manage the hierarchy, availability, and Schema in one place |
| **Create/update ticket** | Select a leaf progressively, complete its Schema, and use inherited ecommerce values |
| **Ticket detail — Summary** | Stats, profile header, linked channels |
| **Ticket detail — Conversation** | Full timeline across all threads; filter by channel/direction; search |
| **Ticket detail — Activity** | Stage change history with who changed it and when |
| **Ticket detail — Sidebar** | ID, status, stage, owner, assignees, ecommerce refs, resolution fields |
| **Tabs** | Resolution, associations, ticket info, metadata JSON |
Dashboard actions call the same backend rules as the API (required fields on resolve/close, assignee validation, etc.).
### Configure ticket classification
Open **CRM → Categories**, choose **New category**, leave the parent empty,
and name the broad problem area, for example `Orders`.
Select the Brands, Channels, and Stores available to its subcategories.
Leave a selector empty to allow all values for that dimension.
Choose **New category** and select its parent. Add a third level when the
classification needs it; otherwise the second level can be the leaf.
Add text, long-text, or option fields to the leaf category. Give fields a
placeholder when it helps the agent and require only essential information.
Save the leaf. The ticket editor now builds the category path one selector
at a time, displays its Schema, and applies the top-level availability.
Category configuration and assignment are currently dashboard-only. Ticket API
responses expose the selected category in `category_id` and its dynamic
field values in `category_answers`; storefront create and patch inputs do not
accept these fields.
***
## Part 6 — Pagination and limits
| Endpoint | Default range | Max range |
| - | - | - |
| `GET /communication/threads` | `from=0`, `to=49` | 100 rows |
| `GET /communication/threads/{id}/messages` | `from=0`, `to=99` | 200 rows |
Use `from`/`to` as inclusive row indices (not page numbers). Example: next page of threads → `from=50&to=99`.
***
## Part 7 — Errors and how to fix them
| HTTP | Typical cause | Fix |
| - | - | - |
| `400` | Business rule failed | Read `details` in body — e.g. missing `resolution_summary`, invalid parent message |
| `404` | Wrong id or wrong account | Check `x-account-id` and UUID |
| `422` | Validation failed | Fix field types/enums; ensure `from <= to` and range limits |
Common `400` messages:
| Message | Meaning |
| - | - |
| `Parent message not found in this thread` | `parent_message_id` is wrong or belongs to another thread |
| `Thread requires a ticket, review, customer, or external id` | Create-thread body missing context fields |
| `subject, body_text, or body_html is required` | Empty message body |
| Stage required field errors | Pass `required_fields` when moving to `resolved` or `closed` |
***
## Part 8 — Automation hooks
Communication on ticket threads emits journey events you can use in [Ticket Journey Triggers](/guides/ticket-journey-triggers):
| Event | Use it to… |
| - | - |
| `TICKET_CUSTOMER_REPLIED` | Escalate priority, notify assignee, start SLA timer |
| `TICKET_AGENT_REPLIED` | Move to `waiting_customer`, send survey |
| `TICKET_MESSAGE_RECEIVED` | Any inbound on ticket — broader than customer-only |
| `TICKET_THREAD_ATTACHED` | Welcome workflow when first channel linked |
Ticket lifecycle events (`TICKET_STAGE_CHANGED`, `TICKET_RESOLVED`, etc.) work the same way.
***
## Frequently asked questions
The **ticket** is the case you track on the board (stage, priority, assignees, resolution). A **thread** is one conversation pipe on that case — usually one email chain or one WhatsApp chat. One ticket, many threads.
Performance. Thread list endpoints return **shells** with
`latest_message_preview` only. Call `GET /communication/threads/{id}/messages`
for full history.
If you integrate with an external inbox: **yes**. They prevent duplicates when
webhooks retry. If you only use the Masivo dashboard manually, Masivo still
generates internal UUIDs — but integrations should always send provider ids.
No. Each message belongs to exactly one thread. To link conversations across
channels, attach multiple threads to the **same ticket**.
Each **inbound** message without `read_at` increments the thread's
`unread_count`. The ticket `communication_summary.unread_count` sums all
threads on that ticket. Mark as read by posting/updating with `read_at` set
(future API) or via dashboard consumption.
Masivo deduplicates review tickets — if an open ticket already exists for that
`review_id`, the API returns the existing ticket instead of creating a second
one.
Public Storefront endpoints documented here are create/list/get only.
Soft-delete exists at the database layer for admin operations but is not
exposed on these routes.
Use the channel that matches how the customer contacted you: `email`, `whatsapp`, `chat`, `review` (auto for review submissions), `system`, or `other`. The channel on a message defaults to its thread's channel if omitted.
***
## Checklist before going to production
* [ ] SERVER API key stored securely on your backend
* [ ] Every create-ticket call sends `idempotency_key`
* [ ] Every synced message sends `external_message_id`
* [ ] Every synced thread sends `external_thread_id` + `channel_account_id`
* [ ] Resolve/close flows collect `resolution_summary` / `closure_reason`
* [ ] Your UI loads thread shells first, messages on demand
* [ ] Journey automations tested for `TICKET_CUSTOMER_REPLIED` and stage changes
***
## Related documentation
* [Tickets & communication (concepts)](/concepts/tickets) — diagrams and field definitions
* [Ticket Journey Triggers](/guides/ticket-journey-triggers) — automate on ticket events
* [Create and manage review forms](/guides/create-and-manage-review-forms) — review → ticket pipeline
* API reference — **Tickets** group in the sidebar
# Manage and track customers
Source: https://docs.masivo.ai/guides/track-customer-activity
Learn how to manage and track customer activity in Masivo
## Managing customers in Masivo
To manage your customers, first navigate to the **Customers** section in the dashboard. You can find them in the left sidebar.
### Creating a new customer
To create a new customer, click the **New customer** button. Fill in the required information such as the customer's name, email, and any additional details you want to track.
Once created, the customer will be automatically assigned to your default tier and a wallet will be created for them.
## Tracking customer activity
To check your customers activity, click the name of the customer you want to track from the customers list.
## Customer overview
You will then be presented with an overview of the current customer activity. How many purchases they have made, how much they have spent, in which tier they are. All of this metrics are measured since the last reporting period.
On the right you will find a summary of its details. Including how many days they have been a customer, they birthday, gender and more.
Finally, on the left side you will find the customer's wallet, transactions and event history.
## Wallet
The wallet is where you can see the customer's current balance. This balance is the sum of all the points they have earned minus the points they have spent.
You can see a total amount of each type of reward they have accumulated. When you expand a reward, you will see a list of all batches that conforms the total amount. Each batch will have a different expiration date, campaign, or something that makes it unique.
When a reward batch is reserved, it will not count towards the total balance. You will notice batches that are reserved since they will be blurred out and contain a reservation date.
## Transactions timeline
The transactions timeline is a list of all the transactions the customer has made. This includes all rewards earned and spent. Is a great way to track from where the customer is earning points and how they are spending them.
# Event history
The event history is a list of all the events that have happened to the customer. This includes when they were registered, whenever they made a purchase, when they were assigned to a tier, and more.
When you expand and event you will see more details about it. For example, how much they spent, In which tier they were assigned, whether an event has been fulfilled or not, and more.
## Related architecture
* [Registration flow](/resources/data-architecture#registration-flow) — What happens when a customer is created in Masivo
* [Activity & events](/resources/data-architecture#activity-events) — How purchases and events appear in the profile and event history
* [Marketing & destinations](/resources/data-architecture#marketing-destinations) — How profile and activity data power audiences and journeys
# Transfer account ownership
Source: https://docs.masivo.ai/guides/transfer-account-ownership
Learn how to transfer Masivo account ownership to another admin member
Account ownership transfer lets the current owner hand the account to another **ADMIN** member without downtime. The recipient must accept the transfer via email.
## Prerequisites
* You must be the **account owner**
* `settings:delete` permission (enforced in the dashboard)
* The recipient must already be an active **ADMIN** on the account
## Transfer flow
Navigate to **Settings → Advanced** (`/settings/advanced`).
In the **Transfer Account** section, click **Transfer**. The button is disabled if you are not the owner.
Choose an **ADMIN** member from the list. The current owner and non-admin members are excluded.
Masivo sends an ownership transfer email to the selected admin. The invitation expires after **24 hours**.
The recipient opens the link in the email and lands on `/auth/accept-invitation?id=…`. They must sign in with the invited email address.
On acceptance, `accounts.owner_id` updates to the new owner. Both the previous and new owner receive confirmation emails. The recipient is redirected to the dashboard with the account cookie set.
## Error states
| Situation | What the recipient sees |
| - | - |
| Invitation expired | Ask the owner to send a new transfer |
| Wrong email signed in | Sign in with the email that received the invitation |
| Owner changed since invite | Transfer was revoked; request a new invitation |
| Recipient is not ADMIN | Must be an active admin to accept |
## Difference from member invitations
| | Ownership transfer | Member invitation |
| - | - | - |
| Initiator | Account owner only | Any user with `settings:write` |
| Recipient | Existing ADMIN member | Any email address |
| On accept | `owner_id` changes | User joins `users_by_accounts` |
| Type | `OWNER_TRANSFER` | `MEMBER` |
## Related documentation
* [Manage teams](/guides/manage-teams)
* [Teams concept](/concepts/teams)
# Using template variables
Source: https://docs.masivo.ai/guides/using-template-variables
Learn what variables are and how they help personalize email, push, and WhatsApp templates
Template variables allow you to personalize content by inserting dynamic
values like a user's name or unsubscribe link.
## What are template variables?
Template variables are placeholders you can use inside email, push, or
WhatsApp templates to automatically insert personalized content.
For example, instead of writing a generic message like:
Hi there!
You can use a variable like:
Hi `{{customer.fullname}}`!
When the message is sent, the variable will be replaced with the actual data of
each recipient, such as "Hi John Doe!"
## Why use variables?
Variables help you:
* Personalize your messages at scale
* Make emails, push notifications, and WhatsApp messages feel more human and
relevant
* Increase engagement by showing context-aware content (like names, product
info, or dates)
## Where can you use variables?
Variables can be used in:
* **Email templates** (subject, header, body, footer)
* **Push templates** (title and body)
* **WhatsApp templates** (header, body, footer, and buttons, depending on
template category)
Masivo replaces the variables at the time of sending, using the actual customer
or event data.
## Common variable types
There are different variable categories available, depending on your use case:
* `customer`: Info about the recipient (e.g. `{{customer.fullname}}`,
`{{customer.email}}`)
* `global`: General values like unsubscribe links (e.g.
`{{global.unsubscribe}}`)
* `event`: Data related to events or transactions (e.g. `{{event.type}}`)
You don't need to memorize them - the template editor will show you which
variables are available depending on the context.
## What happens if a variable is missing?
If a variable doesn't have a value (for example, if a customer's name is
missing), the system might:
* Leave the variable blank
* Show a default fallback if defined
* Or throw an exception
It's a good practice to make sure your data is complete or provide safe
defaults when needed.
You can use preview mode in the editor to test how your message will look
with real data.
If you want to see an example of how a variable is used inside an email
template, check
[How to configure the unsubscribe link](/guides/emails/unsubscribe-links#how-to-configure-the-unsubscribe-link).
## Summary
Template variables allow you to build personalized and dynamic messages without
writing custom content for each user. Use them to improve engagement, build
trust, and make your messages feel more relevant and human.
# Braze integration
Source: https://docs.masivo.ai/integrations/braze
Sync Masivo customers, events, purchases and audiences to Braze
## Campaign attribution
Braze Events sends UTM in `properties.utm_*` on events and each purchase
product, including original context on reversals. This covers split and legacy
Braze. Audience membership has no event UTM payload.
See the [shared attribution guide and tests](/api-reference/guides/attribution-tracking).
## What you can sync
* Customers: create, update, and delete users in Braze
* Events: send behavioral events as Braze custom events
* Purchases: send order line items as Braze purchases
* Audiences: add a custom attribute with the Masivo audience name
## Prerequisites
* A Braze REST API Key with permissions to create users, track events, and
purchases
Your Masivo customer `id` will be used as Braze `external_id`
## Rate limits and batching
Braze has specific limits that Masivo respects automatically:
* **Rate limit**: 3,000 calls every 3 seconds
* **Batch size**: Each call can include up to 75 attributes, 75 events, and 75 purchases
* **User deletion limit**: 50 user deletions per request (Braze API limitation)
* Masivo groups messages by account and sends them in optimal batches to stay within these limits
## Configuration
1. Go to Settings → Integrations → Braze in the Masivo dashboard.
2. Paste your Braze API Key.
3. **Configure your Braze REST Endpoint**: Each Braze account has a unique REST Endpoint (e.g., `https://rest.iad-01.braze.com` for US East, `https://rest.fra-01.braze.com` for EU).
4. Enable the toggles you need:
* Audiences
* Events
* Purchases (only fulfilled purchases are synced)
* Customer tags
* Customers: Create, Update, Delete
Make sure to use the correct Braze REST Endpoint for your account. Using the
wrong REST Endpoint will cause API calls to fail. You can find your account's
REST Endpoint in the Braze dashboard under **Settings → API Settings**.
## How it works
Once configured, Masivo automatically syncs your data to Braze:
1. **Real-time sync**: Data is sent to Braze as soon as events occur in Masivo
2. **Smart validation**: Masivo checks your configuration and only sends data that meets your criteria
3. **Efficient processing**: Data is batched and sent in optimal groups to respect Braze's limits
4. **Error handling**: Failed syncs are logged and don't affect other integrations
### What gets validated
Before sending data to Braze, Masivo validates:
* Your API configuration is correct
* The specific features you want to sync are enabled
* Data meets your business rules (e.g., only fulfilled purchases)
* Events are not internal system events
## How purchases work in Braze
**Purchases are additive, not replacement-based:**
* If the user already exists in Braze, the `purchases` block **adds the purchase to their history** (doesn't delete previous ones)
* If the user doesn't exist, Braze creates the user and then registers the purchase
* Each purchase has its own identifier and timestamp, maintaining a complete transaction history
* **Reversals and cancellations** are handled by sending additional purchases with negative prices
This approach allows Braze to calculate accurate Lifetime Revenue by summing all purchases (including negative ones) while preserving the complete purchase history.
## Event properties behavior
When sending events with custom properties:
* **Flat key-value properties are supported** but won't automatically appear in the user profile UI
* **Only flat properties** (not nested objects) are properly visible and segmentable
* Masivo flattens complex event data before sending to ensure compatibility
Protected internal events such as REGISTRATION, FULFILL, REVERSE, EXPIRE,
CANCEL, REDEEM\_COUPON and TIER\_ADJUSTMENT are not forwarded to Braze.
## Audience synchronization
### Audience calculation operations
* Sends audience information as custom attributes to Braze
* Requires `audience_id` in your data
* **Automatic prefix**: Masivo adds a "masivo\_" prefix to audience names to prevent conflicts with existing Braze audiences
### Audience DELETE operations
* Automatically gathers all customers in the audience before deletion
* Sends complete customer list to Braze for proper cleanup
* Ensures Braze has all necessary information for audience removal
When deleting audiences, Masivo automatically collects all customer data to
ensure Braze can properly clean up the audience information.
Masivo automatically prefixes audience names with "masivo\_" to avoid naming
conflicts with existing Braze audiences. For example, an audience named "VIP
Customers" in Masivo will appear as "masivo\_VIP Customers" in Braze.
# Google Ads integration
Source: https://docs.masivo.ai/integrations/google-ads
Track and send conversion data from Masivo to Google Ads
## Campaign attribution
Conversions accept click IDs inside `attribution` and legacy top-level fields.
The uploader sends one of `gclid`, `gbraid` or `wbraid`, in that order. It does
not upload arbitrary UTM or substitute enhanced-conversion customer identifiers.
See the [shared attribution guide and tests](/api-reference/guides/attribution-tracking).
## What you can sync
* **Purchase conversions**: Track completed purchases as conversions in Google Ads
* **Event conversions**: Track custom events (sign-ups, leads, etc.) as conversions
* **Conversion values**: Send transaction amounts to measure ROAS (Return on Ad Spend)
* **Order IDs**: Link purchases to specific transactions for better tracking
Looking for **audience / Customer Match** sync? That is a separate destination:
[Google Ads Remarketing](/guides/CDP/integrations/google-ads-remarketing/overview).
Remarketing uses the Data Manager API and does **not** require a developer
token.
## Prerequisites
Before setting up the integration, you'll need:
* A Google Ads account with active campaigns
* Google Ads API access (requires approval from Google)
* A Conversion Action created in Google Ads
* OAuth 2.0 credentials from Google Cloud Console
Getting API access requires applying through the [Google Ads API
Center](https://ads.google.com/aw/apicenter). Basic access is typically
approved within 24 hours for accounts with a billing history.
## Getting your credentials
### 1. Customer ID
Your Google Ads Customer ID is the 10-digit account number:
1. Log in to [Google Ads](https://ads.google.com)
2. Look for the account number at the top of the page (format: `123-456-7890`)
3. Enter in Masivo with or without dashes (e.g., `123-456-7890` or `1234567890`)
Masivo automatically removes dashes from Customer IDs, so you can enter them
in either format.
### 2. Developer Token
Request API access from Google Ads:
1. Go to **Tools & Settings → API Center** in Google Ads
2. Click **Apply for access**
3. Fill out the application form
4. Once approved, copy your **Developer Token**
Developer tokens are sensitive credentials. Never share them publicly or
commit them to version control.
### 3. OAuth Credentials
Create OAuth 2.0 credentials in Google Cloud Console:
1. Go to [Google Cloud Console](https://console.cloud.google.com)
2. Create a new project or select an existing one
3. Enable the **Google Ads API**
4. Go to **APIs & Services → Credentials**
5. Click **Create Credentials → OAuth client ID**
6. Choose **Web application** as application type
7. Add authorized redirect URIs (e.g., `http://localhost:3000/oauth2callback`)
8. Save and copy your **Client ID** and **Client Secret**
### 4. Refresh Token
Generate a refresh token using OAuth 2.0 flow:
1. Use Google's OAuth 2.0 Playground or implement the OAuth flow
2. Request scopes: `https://www.googleapis.com/auth/adwords`
3. Complete the authorization flow
4. Exchange the authorization code for tokens
5. Copy the **Refresh Token** (it won't expire unless revoked)
You can use the [OAuth 2.0
Playground](https://developers.google.com/oauthplayground) to quickly generate
a refresh token. Just configure it with your Client ID and Client Secret.
### 5. Conversion Action ID
Create a conversion action in Google Ads:
1. Go to **Tools & Settings → Conversions** in Google Ads
2. Click **+ New conversion action**
3. Choose **Import** → **Other data sources**
4. Select **Track conversions from clicks**
5. Configure your conversion settings:
* **Category**: Purchase, Sign-up, Lead, etc.
* **Value**: Use different values for each conversion
* **Count**: Every conversion (for purchases) or One (for sign-ups)
* **Click-through conversion window**: 30-90 days recommended
* **Attribution model**: Choose based on your business
6. Save and copy the **Conversion Action ID** (visible in the conversion details URL or settings)
## Configuration
Once you have all credentials:
1. Go to **Settings → Integrations → Google Ads** in the Masivo dashboard
2. Enter your credentials:
* **Customer ID**: Your 10-digit account number (with or without dashes)
* **Developer Token**: From Google Ads API Center
* **Client ID**: From Google Cloud Console
* **Client Secret**: From Google Cloud Console
* **Refresh Token**: Generated via OAuth flow
* **Conversion Action ID**: From your conversion action
3. Enable the features you want to sync:
* **Events**: Track custom event conversions
* **Purchases**: Track completed purchase conversions
4. Click **Save** to activate the integration
## How it works
Google Ads conversion tracking works by connecting ad clicks to conversions on your site or app:
### 1. Click tracking
When a user clicks your Google Ad, Google automatically adds tracking parameters to the landing URL:
* **gclid** (Google Click ID): Used for web traffic
* **gbraid** (Google Bounce Rate Analysis ID): Used for iOS 14.5+ traffic
* **wbraid** (Web to App Bounce Rate Analysis ID): Used for web-to-app conversions
### 2. Capturing click identifiers
Your website or app must capture these parameters:
```javascript theme={null}
// Example: Capture gclid from URL
const urlParams = new URLSearchParams(window.location.search);
const gclid = urlParams.get("gclid");
const gbraid = urlParams.get("gbraid");
const wbraid = urlParams.get("wbraid");
// Store for later use (localStorage, cookie, or session)
if (gclid) localStorage.setItem("gclid", gclid);
if (gbraid) localStorage.setItem("gbraid", gbraid);
if (wbraid) localStorage.setItem("wbraid", wbraid);
```
These identifiers are **required** for conversion tracking. Without them,
Masivo cannot attribute conversions to your Google Ads campaigns.
### 3. Sending conversions
When a conversion happens (purchase, sign-up, etc.), send the event to Masivo **with** the stored click identifier:
```json theme={null}
{
"type": "PURCHASE",
"customer_id": "cust_123",
"gclid": "EAIaIQobChMI0tXXyPaE8wIVjRmtBh3XqAZ7EAAYASAAEgJqxfD_BwE",
"order": {
"purchase_id": "ORD-789",
"value": 159.99,
"discounted_value": 149.99
},
"currency": "USD",
"issued_at": "2025-10-20T14:30:00Z"
}
```
### 4. Automatic sync
Masivo automatically:
* Validates the event and configuration
* Transforms the data to Google Ads format
* Sends the conversion to Google Ads API
* Handles errors and retries if needed
## Data structure
### Purchase conversion
```json theme={null}
{
"type": "PURCHASE",
"customer_id": "cust_123",
"brand_id": "0001",
"gclid": "EAIaIQobChMI0tXXyPaE8wIVjRmtBh3XqAZ7EAAYASAAEgJqxfD_BwE",
"order": {
"purchase_id": "ORD-789",
"value": 159.99,
"discounted_value": 149.99,
"payment_method": "CREDIT",
"products": [
{
"sku": "PROD-001",
"value": 79.99,
"amount": 2
}
]
},
"currency": "USD",
"fulfilled": true,
"issued_at": "2025-10-20T14:30:00Z",
"fulfilled_at": "2025-10-20T14:30:00Z"
}
```
### Event conversion
```json theme={null}
{
"type": "EVENT",
"customer_id": "cust_123",
"brand_id": "0001",
"gclid": "EAIaIQobChMI0tXXyPaE8wIVjRmtBh3XqAZ7EAAYASAAEgJqxfD_BwE",
"event_name": "lead_signup",
"value": 25.0,
"currency": "USD",
"issued_at": "2025-10-20T14:30:00Z"
}
```
### iOS conversion (using gbraid)
For iOS 14.5+ users where gclid is not available:
```json theme={null}
{
"type": "PURCHASE",
"customer_id": "cust_123",
"gbraid": "0AAAAAo4mICNBGfEXAMPLEExampleGbraidString",
"order": {
"purchase_id": "ORD-790",
"value": 99.99
},
"currency": "USD",
"issued_at": "2025-10-20T14:30:00Z"
}
```
## Required fields
### For all conversions
| Field | Type | Description |
| - | - | - |
| `type` | string | Either `PURCHASE` or `EVENT` |
| `customer_id` | string | Unique customer identifier |
| `gclid` OR `gbraid` OR `wbraid` | string | **Required**: Click identifier from ad |
| `issued_at` | string | ISO 8601 timestamp of the conversion |
| `currency` | string | ISO 4217 currency code (default: `USD`) |
### For purchases
| Field | Type | Description |
| - | - | - |
| `order.value` | number | Total order value |
| `order.purchase_id` | string | Unique order identifier |
| `order.discounted_value` | number | Value after discounts (optional) |
### For events
| Field | Type | Description |
| - | - | - |
| `value` | number | Conversion value (for lead value, etc.) |
| `event_name` | string | Name of the custom event (optional) |
## Understanding click identifiers
### GCLID (Google Click ID)
* **Format**: Long alphanumeric string (e.g., `EAIaIQobChMI0tXXyPaE8wIVjRmtBh3XqAZ7EAAYASAAEgJqxfD_BwE`)
* **Usage**: Standard web tracking
* **Platform**: All web browsers
* **Expiration**: Valid for 90 days after click
* **Priority**: Use gclid if available
### GBRAID (Google Bounce Rate Analysis ID)
* **Format**: Alphanumeric string starting with specific prefix
* **Usage**: iOS 14.5+ tracking without cookies
* **Platform**: Safari on iOS/iPadOS
* **Expiration**: Valid for click-through conversion window
* **Priority**: Use when gclid is not available (iOS)
### WBRAID (Web to App Bounce Rate Analysis ID)
* **Format**: Alphanumeric string
* **Usage**: Web-to-app conversion tracking
* **Platform**: Mobile apps (iOS/Android)
* **Expiration**: Valid for click-through conversion window
* **Priority**: Use when tracking web clicks that convert in-app
**Priority order**: Always use `gclid` if available. Only use `gbraid` or
`wbraid` when `gclid` is not present. Never use multiple identifiers in the
same conversion.
## Conversion value logic
Masivo automatically determines the conversion value based on event type:
### For PURCHASE events
```javascript theme={null}
// Uses order.value or order.discounted_value
conversionValue = order.value || order.discounted_value || 0;
```
### For EVENT events
```javascript theme={null}
// Uses the event value directly
conversionValue = data.value || 0;
```
## Validation
Before sending conversions to Google Ads, Masivo validates:
### Configuration validation
* ✅ Customer ID is exactly 10 digits (dashes are automatically removed if present)
* ✅ Developer token is provided
* ✅ OAuth credentials (client\_id, client\_secret, refresh\_token) are complete
* ✅ Conversion Action ID is configured
### Message validation
* ✅ Event type is enabled (events or purchases)
* ✅ At least one click identifier (gclid, gbraid, or wbraid) is present
* ✅ Customer ID exists
* ✅ Timestamp is in valid ISO 8601 format
* ✅ For purchases: order object with value exists
If any required field is missing, the conversion will be **skipped** and
logged. Check your event payload to ensure all required fields are included.
## Best practices
### 1. Always capture click identifiers
Implement tracking on every landing page:
```javascript theme={null}
// Capture and persist on page load
const captureClickId = () => {
const params = new URLSearchParams(window.location.search);
const gclid = params.get("gclid");
const gbraid = params.get("gbraid");
const wbraid = params.get("wbraid");
if (gclid) {
localStorage.setItem("gclid", gclid);
// Set expiration (90 days)
localStorage.setItem(
"gclid_expires",
Date.now() + 90 * 24 * 60 * 60 * 1000
);
}
if (gbraid) localStorage.setItem("gbraid", gbraid);
if (wbraid) localStorage.setItem("wbraid", wbraid);
};
captureClickId();
```
### 2. Check expiration before sending
```javascript theme={null}
const getValidGclid = () => {
const gclid = localStorage.getItem("gclid");
const expires = localStorage.getItem("gclid_expires");
if (gclid && expires && Date.now() < parseInt(expires)) {
return gclid;
}
return null;
};
```
### 3. Send conversions immediately
Track conversions as soon as they happen:
```javascript theme={null}
// After successful purchase
const trackPurchase = async orderData => {
const gclid = getValidGclid();
await fetch("/api/events/track", {
method: "POST",
body: JSON.stringify({
type: "PURCHASE",
customer_id: user.id,
gclid: gclid, // Include if available
order: orderData,
currency: "USD",
issued_at: new Date().toISOString()
})
});
};
```
### 4. Use accurate conversion values
* For purchases: Use the actual charged amount (after discounts)
* For events: Use realistic value estimates (e.g., average lead value)
* Always include currency code
### 5. Set up conversion tracking early
The more conversion data Google Ads has, the better its automated bidding:
* Start tracking from day one
* Track multiple conversion types (purchases, sign-ups, leads)
* Be consistent with value attribution
### 6. Monitor conversion import status
Check your Google Ads account regularly:
1. Go to **Tools & Settings → Conversions**
2. Select your conversion action
3. Check the **Status** column for import errors
4. Review conversion counts match your expectations
## Limitations
* **Click identifiers are required**: Conversions without gclid/gbraid/wbraid will be skipped
* **90-day attribution window**: gclid values expire after 90 days (or your configured window)
* **Real identifiers only**: Cannot use mock or fabricated click identifiers
* **API rate limits**: Subject to Google Ads API rate limits (typically not an issue)
* **Approval required**: Requires approved Google Ads API access
Google validates all click identifiers cryptographically. Fabricated or
expired identifiers will be rejected with a validation error.
## Troubleshooting
### Conversions not showing in Google Ads
**Check click identifier capture:**
```javascript theme={null}
// Debug: Log captured values
console.log("gclid:", localStorage.getItem("gclid"));
console.log("gbraid:", localStorage.getItem("gbraid"));
```
**Verify integration is enabled:**
* Settings → Integrations → Google Ads
* Ensure "Purchases" or "Events" toggle is ON
**Check customer ID format:**
* Must be 10 digits (with or without dashes)
* Example: `8623528631` ✅
* Example: `862-352-8631` ✅ (dashes are automatically removed)
### "Invalid gclid" errors
* **Cause**: Expired, modified, or fabricated gclid
* **Solution**: Ensure you're capturing the gclid directly from the URL without modification
* **Note**: gclid values are cryptographically signed and validated by Google
### "Configuration incomplete" errors
Check that all credentials are configured:
```bash theme={null}
✅ Customer ID (10 digits, with or without dashes)
✅ Developer Token
✅ Client ID
✅ Client Secret
✅ Refresh Token
✅ Conversion Action ID
```
### "Conversion action not found" errors
* Verify the Conversion Action ID is correct
* Ensure the conversion action is active in Google Ads
* Check that it's configured for "clicks" (not "views")
### "Authentication failed" errors
* **Refresh token expired**: Regenerate your refresh token via OAuth flow
* **Invalid credentials**: Double-check client\_id and client\_secret
* **API access revoked**: Verify your API access status in Google Ads
### Conversions delayed or not appearing
* **Normal delay**: Google Ads can take 3-9 hours to process imported conversions
* **Check import history**: Tools & Settings → Conversions → Import history
* **Verify timestamps**: Ensure `issued_at` is accurate and recent
## Testing your integration
### 1. Create a test conversion action
Set up a separate conversion action for testing:
1. Go to Google Ads → Tools & Settings → Conversions
2. Create a new conversion action with "Test" in the name
3. Use a low value to identify test conversions
4. Use this Conversion Action ID in your test environment
### 2. Generate a real gclid
You cannot fabricate valid gclid values. To test:
1. Create a test campaign in Google Ads
2. Click your own ad from a real browser
3. Capture the gclid from the landing page URL
4. Use this gclid in your test conversion (valid for 90 days)
### 3. Use validation mode
Temporarily enable validation in development:
```typescript theme={null}
// In your codebase (for testing only)
const p = {
conversions: [conversion],
partial_failure: true,
validate_only: true, // Test without recording conversion
customer_id: cleanCustomerId
};
```
With `validate_only: true`, Google Ads validates your conversion data without
recording it. This is useful for testing configuration and data format.
### 4. Monitor the response
Check Masivo logs for integration status:
```json theme={null}
// Success response
{
"processedCount": 1,
"errorCount": 0
}
// Error response
{
"error": "The imported gclid could not be decoded...",
"details": "conversions[0].gclid"
}
```
## Common integration patterns
### E-commerce checkout
```javascript theme={null}
// 1. Capture gclid on landing
useEffect(() => {
const params = new URLSearchParams(window.location.search);
const gclid = params.get("gclid");
if (gclid) sessionStorage.setItem("gclid", gclid);
}, []);
// 2. Send conversion after purchase
const handlePurchaseComplete = async order => {
const gclid = sessionStorage.getItem("gclid");
await trackEvent({
type: "PURCHASE",
customer_id: user.id,
gclid: gclid,
order: {
purchase_id: order.id,
value: order.total,
discounted_value: order.discountedTotal
},
currency: "USD",
issued_at: new Date().toISOString()
});
};
```
### Lead generation
```javascript theme={null}
// After form submission
const handleFormSubmit = async formData => {
const gclid = localStorage.getItem("gclid");
await trackEvent({
type: "EVENT",
customer_id: formData.email,
gclid: gclid,
event_name: "lead_form_submission",
value: 50.0, // Estimated lead value
currency: "USD",
issued_at: new Date().toISOString()
});
};
```
### Mobile app (with gbraid)
```javascript theme={null}
// React Native or Expo
import { useURL } from "expo-linking";
const captureClickIds = () => {
const url = useURL();
if (url) {
const params = new URL(url).searchParams;
const gbraid = params.get("gbraid");
if (gbraid) {
AsyncStorage.setItem("gbraid", gbraid);
}
}
};
```
## Next steps
After setting up the integration:
1. **Test thoroughly**: Verify conversions appear in Google Ads (allow 3-9 hours)
2. **Enable automated bidding**: Use conversion data for Smart Bidding strategies
3. **Set up value-based bidding**: Optimize for conversion value, not just count
4. **Track multiple actions**: Set up different conversion actions for different goals
5. **Monitor performance**: Check conversion import status regularly
Need help? Check our [API Reference](/api-reference/events/create) for more
details on sending events to Masivo, or contact support for integration
assistance.
# Google Analytics 4 integration
Source: https://docs.masivo.ai/integrations/google-analytics
Preserve campaign attribution and send web and app events to GA4
Masivo sends events to Google Analytics 4 through Measurement Protocol. For
session attribution, these events must join the identity and session collected
by your website's Google tag or your app's Firebase Analytics SDK. Masivo link
tracking in its own analytics does not automatically create a GA4 session.
## Configure streams
In the Google Analytics destination, configure at least one complete stream:
| Platform | Settings | Identity supplied with events |
| - | - | - |
| Web | Web Measurement ID (`G-...`) and Web API Secret | `ga4.client_id` |
| Android | Android Firebase App ID (`1:...:android:...`) and API Secret | `ga4.app_instance_id` |
| iOS | iOS Firebase App ID (`1:...:ios:...`) and API Secret | `ga4.app_instance_id` |
Each secret belongs to its corresponding GA4 data stream. Keep these secrets
in Masivo's destination configuration, never in links or client apps. Enable
**Sync events** and/or **Sync purchases**, and include the desired event names
in the allowed-events selection when using a restricted list.
Existing `measurement_id` and `api_secret` settings remain the web stream.
App settings are stored as `android` and `ios` objects, each containing
`firebase_app_id` and `api_secret`. An app-only destination is supported.
**Test connection** validates a sample payload for each configured stream using
Google's validation endpoint. It does not validate credentials or create events
in reports. Verify receipt separately using the actual stream and identity.
## Campaign links
Masivo preserves `utm_source`, `utm_medium`, `utm_campaign`, `utm_term`, and
`utm_content`. Non-empty incoming values override the stored destination's
values. Missing destination fields first use the selected fallback URL,
including campaign fields inside a Play Store `referrer`. Only fields still
missing on internal campaign links receive these defaults:
| Channel | Source | Medium |
| - | - | - |
| Email | `masivo` | `email` |
| Push | `masivo` | `push` |
| WhatsApp | `masivo` | `whatsapp` |
The default campaign is the link's `campaign_id`, or `template_id` if there is
no campaign. Explicit values remain unchanged. In-app/news-feed navigation
and external links do not receive automatic acquisition labels. Existing
links use these rules when clicked, without rewriting stored links.
For example, a Braze email should explicitly carry `utm_source=braze` and
`utm_medium=email`. A Meta ad can use `utm_source=facebook` and
`utm_medium=paid_social`, with its campaign in `utm_campaign`. Masivo preserves
these labels rather than replacing the source with the name of the shortener.
`fbclid` alone does not provide a manually tagged GA4 campaign.
The same UTM context travels through the direct destination, HTTPS fallback,
Android Intent and installed-app resolution (`$app-found=true`). Google Play
receives UTM inside its encoded `referrer`, alongside Masivo's `tracking_id`
and `link`. Existing referrer fields are preserved. Editing UTM in the dashboard
also preserves unrelated query parameters and URL fragments.
## Event context
The existing `POST /storefront/v1/behavior/events` request accepts an optional
`ga4` object. Client SDK events accept the same object; it is forwarded with
the event rather than collected automatically.
| Field | Meaning |
| - | - |
| `platform` | Required when `ga4` is supplied: `web`, `android`, or `ios` |
| `client_id` | Real Google tag client ID; web only |
| `app_instance_id` | Firebase's 32-character hexadecimal installation ID; Android/iOS only |
| `session_id` | Session where the event occurred; positive integer or numeric string on all platforms |
| `user_id` | Optional non-PII User-ID also explicitly configured in the Google tag/Firebase SDK |
| `engagement_time_msec` | Optional measured engagement since the preceding event, in milliseconds |
Masivo rejects malformed contexts and identities for the wrong platform.
Requests do not accept API secrets in this object. `customer_id` remains the
Masivo customer identifier; it does not replace a real GA4 identity.
### Web
Keep campaign parameters in the landing URL until the Google tag processes
it. Obtain IDs through the tag API instead of parsing cookies:
```javascript theme={null}
// Run after the Google tag has initialized, with analytics consent granted.
const measurementId = "G-XXXXXXXXXX";
const getGA = field =>
new Promise(resolve => {
gtag("get", measurementId, field, resolve);
});
const [client_id, session_id] = await Promise.all([
getGA("client_id"),
getGA("session_id")
]);
const ga4 = { platform: "web", client_id, session_id };
// Include ga4 in your checkout request to your own backend.
// That backend forwards it with the confirmed Masivo PURCHASE event.
```
Capture context when the event occurs. Do not keep a session ID indefinitely
or reuse one from a different device. Follow the existing consent flow; if
analytics is disabled, do not call the tag API or send analytics data.
### Android and iOS
The host app supplies Firebase's installation and session IDs using its
Analytics SDK (`getAppInstanceId`/`getSessionId` on Android,
`appInstanceID`/`sessionID` on iOS). It handles incoming universal/app links
and custom schemes and passes their campaign information to Firebase.
After `masivo.deepLinks.resolveDeferred(...)` returns a recovered URL, read its
UTM parameters. If Firebase has not already recorded that campaign, log
`campaign_details` with `source`, `medium`, `campaign`, `term`, and `content`.
For first-install attribution, do this shortly after installation; Firebase
owns `first_open`, so do not emit a synthetic `first_open` from Masivo.
Preserve the returned `tracking_id` for Masivo attribution. Its value is not
an `app_instance_id`. Resolution may return no match; iOS fingerprint matches
are probabilistic and do not establish deterministic installation attribution.
No Firebase dependency is required by the Masivo SDK itself.
See Google's [app attribution guide](https://support.google.com/analytics/answer/10311900?hl=en)
and [identity reference](https://developers.google.com/analytics/devguides/collection/protocol/ga4/reference).
## Purchases
Send confirmed purchases from your backend using a SERVER API key. The client
SDK does not authorize protected `PURCHASE` events. Forward the checkout's
captured GA4 context with the purchase:
```json theme={null}
{
"type": "PURCHASE",
"customer_id": "cust_123",
"brand_id": null,
"fulfilled": true,
"currency": "USD",
"ga4": {
"platform": "web",
"client_id": "123456789.1720000000",
"session_id": 1720000000
},
"attribution": {
"utm_source": "braze",
"utm_medium": "email",
"utm_campaign": "welcome"
},
"order": {
"purchase_id": "ORD-789",
"value": 20,
"products": [{ "sku": "SKU-001", "amount": 2, "value": 10 }],
"payment_method": "CREDIT"
}
}
```
The IDs above are placeholders; replace them with the actual browser/app
context. Masivo maps the order's ID to `transaction_id`, products to `items`,
`sku` to `item_id`, `amount` to `quantity`, and unit `value` to `price`.
`discounted_value` takes precedence when supplied, including zero. Currency
comes from the event or the account's currency. Explicit native GA4 ecommerce
fields take precedence over the equivalent order mapping.
Unfulfilled Masivo orders wait for confirmation. Cancelled orders do not
produce purchases. Reversals become `refund` with the original transaction
ID and the reversal's timestamp. Select one purchase emitter per flow: either the client tag/Firebase or
this connector. Do not independently send the same purchase through both.
Standard/ecommerce event names are lowercased. Other custom event parameters
retain the existing `event_` flattening, except campaign UTM and GA4 session
context and `debug_mode`, which are sent without that prefix. Internal
`_type` and `_source_direction` metadata are excluded. Invalid GA4 event names
are moved to the error queue instead of being sent and silently rejected. Explicit `campaign_details`
events use GA4's native campaign field names; Masivo does not create a new
campaign event for every purchase.
## Legacy fallback and delivery
When a real identity is absent, Masivo preserves the previous behavior:
`customer_id` is used as `client_id` on the web stream (`anonymous` if the
queued data has no customer ID). A supplied session is omitted on this path
because it belongs to a different identity. This compatibility path does
not provide reliable session attribution. It requires a configured web stream,
even when the event originated in an app.
Valid app identities always select their app stream. Missing stream
configuration is reported and queued messages remain pending; they are not
silently sent to another stream. The cron logs per-account
`legacyIdentityCount` and `missingSessionCount` for each processing batch.
These count prepared events, not confirmed GA4 ingestion.
Batches contain up to 25 events and separate platforms, identities and User-IDs.
Each event retains its own session and `issued_at` timestamp. Session attribution
requires the corresponding online session, delivery within 24 hours of its
start and an event timestamp within that session. See Google's
[Measurement Protocol requirements](https://developers.google.com/analytics/devguides/collection/protocol/ga4/use-cases).
## Verify the integration
1. Click a test link for Meta, Braze and Masivo. Check every destination URL
and the decoded Play referrer for source, medium and campaign.
2. Test web, installed app and fresh install. Verify that the tag/Firebase
receives campaign context and generates the real identity/session.
3. Send a confirmed test purchase from that session. Inspect its final payload,
transaction ID, items, amount and currency, and check for duplicate emitters.
4. Validate payloads with `/debug/mp/collect` and
`validation_behavior: ENFORCE_RECOMMENDATIONS`. This endpoint does not
collect events or verify secrets. A `2xx` from `/mp/collect` alone does not
prove that GA4 processed the event.
5. Verify actual receipt using a test property and inspect acquisition,
session and purchase reporting after processing. `campaign_details` is
not displayed in reports or DebugView; it is available in BigQuery export
when enabled. Check its effect on attributed events instead.
For a test event, send top-level `debug_mode: true` alongside `ga4`. Masivo
preserves it for standard and custom events. Enable debugging in the
Google tag/Firebase implementation as well, select that device in DebugView,
and include actual measured `ga4.engagement_time_msec` for Realtime checks.
Remove debug mode when testing normal acquisition reports: an active
developer-traffic filter may exclude debug events from those reports. See
[Google’s developer traffic filter guide](https://support.google.com/analytics/answer/13296662?hl=en).
Historical attribution and unmatched installs cannot be reconstructed by
changing link parameters. See Google's
[validation guide](https://developers.google.com/analytics/devguides/collection/protocol/ga4/validating-events)
and [campaign\_details reference](https://developers.google.com/analytics/devguides/collection/protocol/ga4/reference/events#campaign_details).
# Integrations
Source: https://docs.masivo.ai/integrations/index
Connect third‑party tools to sync data from Masivo
## Overview
Masivo provides an integrations pipeline to synchronize your customer data
and activity with external providers. Once enabled, Masivo will enqueue
messages when relevant events happen, batch them efficiently, and deliver
them to the target platform.
## What gets synced
* Customers: creation, updates, deletions, and optional customer tags
* Events: behavioral events generated by your applications
* Purchases: order and product information
* Audiences: customer membership reflected as custom attributes
## Attribution across integrations
Send captured campaign and matching fields in `attribution` on events and
backend purchases. See the [shared destination matrix and test guide](/api-reference/guides/attribution-tracking)
for Braze, Meta, TikTok, GA4, Google Ads, S3 and webhooks. Audience destinations
synchronize membership; GA4 additionally uses its own `ga4` session context.
## How it works
1. Enable and configure the integration in the dashboard under
Settings → Integrations.
2. Masivo automatically syncs your data when customers are created/updated/deleted,
when events occur, when purchases are fulfilled, and when audiences are recalculated.
3. Data is processed efficiently and sent to your chosen platform in optimal batches.
### Smart validation
Before sending data, Masivo validates:
* Your configuration is correct
* Required features are enabled
* Data meets your business rules
* Events are appropriate for the target platform
### Efficient processing
* Data is batched automatically to respect platform limits
* Failed syncs don't affect other integrations
* Real-time processing ensures timely data delivery
Masivo batches provider requests. For example, Braze is limited to 75 items
per request for `attributes`, `events`, and `purchases`.
## Common requirements
* A valid API key for the target provider
* Proper permissions configured for the integration features you want to use
## Next steps
Explore the provider‑specific guide to see exact payloads, feature toggles,
and field mappings.
Sync customers, events, purchases, and audience attributes to Braze
Send e-commerce, standard, and custom events to Google Analytics 4
Track and send conversion data from purchases and events to Google Ads
Sync Masivo audiences to Google Ads Customer Match lists (Data Manager API)
Track conversions and optimize ad campaigns with TikTok Events API
# TikTok Events API integration
Source: https://docs.masivo.ai/integrations/tiktok
Sync Masivo customers, events, and conversions to TikTok for ad optimization
## Campaign attribution
TikTok Events sends `properties.utm_*`, `user.ttclid`, `user.ttp` and
`page.url` from captured context. This covers split and legacy TikTok.
Audience membership has no event UTM payload.
See the [shared attribution guide and tests](/api-reference/guides/attribution-tracking).
## What you can sync
* **Conversion events**: Complete funnel tracking from ViewContent to CompletePayment
* **Standard events**: User engagement, navigation, and interaction tracking
* **Custom events**: Any business-specific events you want to track
* **Audiences**: Sync audience membership for custom audience targeting
## Prerequisites
* A TikTok Ads account
* A TikTok Pixel installed on your website or app
* A Pixel Code (format: `C9XXXXXXXXXXXXXXXXXX`)
* An Access Token for TikTok Events API
You can find your Pixel Code in TikTok Ads Manager under **Assets → Events →
Web Events → Manage**. Generate an Access Token under **Tools → Events Manager
→ Settings → Generate Access Token**.
## Event types
TikTok Events API supports standard conversion events that Masivo automatically maps and transforms:
### Standard conversion events (14 events)
These events track the complete customer journey and optimize your TikTok ad campaigns:
| Event | When to send | Key parameters | Masivo mapping | How to send |
| - | - | - | - | - |
| `ViewContent` | User views a product or page | `contents`, `content_type`, `value` | `view_item` | Auto-mapped |
| `ClickButton` | User clicks a button | `contents`, `description` | — | Use `"ClickButton"` |
| `Search` | User performs a search | `query`, `contents` | `search` | Auto-mapped |
| `Contact` | User initiates contact | `description` | — | Use `"Contact"` |
| `Download` | User downloads content | `contents`, `description` | — | Use `"Download"` |
| `SubmitForm` | User submits a form | `contents`, `description` | — | Use `"SubmitForm"` |
| `CompleteRegistration` | User creates an account | `contents`, `value` | `sign_up` | Auto-mapped |
| `Subscribe` | User subscribes to service | `contents`, `value` | — | Use `"Subscribe"` |
| `AddToWishlist` | User adds item to wishlist | `contents`, `value`, `currency` | `add_to_wishlist` | Auto-mapped |
| `AddToCart` | User adds item to cart | `contents`, `value`, `currency` | `add_to_cart` | Auto-mapped |
| `InitiateCheckout` | User starts checkout process | `contents`, `value`, `currency` | `begin_checkout` | Auto-mapped |
| `AddPaymentInfo` | User adds payment information | `contents`, `value`, `currency` | `add_payment_info` | Auto-mapped |
| `PlaceAnOrder` | User places an order | `contents`, `value`, `currency` | — | Use `"PlaceAnOrder"` |
| `CompletePayment` | User completes a purchase | `contents`, `value`, `currency`, `order_id` | `PURCHASE` | Auto-mapped |
**Auto-mapped events**: Masivo automatically converts common event names (like `purchase` or `add_to_cart`) to TikTok's standard names.
**Use exact name events**: For events marked with "Use `EventName`", send that exact TikTok event name in the `type` field.
### Custom events
Events not in the table above are sent as custom events with flattened parameters prefixed with `custom_`. Campaign UTM are also available under their original `properties.utm_*` names.
Example of a custom event:
```json theme={null}
{
"type": "video_watched_50",
"customer_id": "cust_123",
"video_id": "vid_789",
"duration_seconds": 120
}
```
This becomes a custom event in TikTok with parameters: `custom_video_id`, `custom_duration_seconds`.
## Configuration
### Step 1: Get your TikTok credentials
1. Log in to [TikTok Ads Manager](https://ads.tiktok.com)
2. Navigate to **Assets → Events → Web Events**
3. Select your Pixel or create a new one
4. Copy your **Pixel Code** (starts with `C9`)
5. Go to **Settings → Generate Access Token**
6. Copy the generated **Access Token**
Keep your Access Token secure. It provides access to send events to your
TikTok Pixel. Never share it publicly or commit it to version control.
### Step 2: Configure in Masivo
1. Go to **Settings → Integrations → Destinations** in the Masivo dashboard
2. Click **Add Destination** and select **TikTok**
3. Enter your **Pixel Code** (format: `C9XXXXXXXXXXXXXXXXXX`)
4. Enter your **Access Token**
5. Enable the features you want to sync:
* **Events**: All standard conversion events
* **Purchases**: Only fulfilled purchases
* **Audiences**: Calculate and Delete operations
## How it works
Once configured, Masivo automatically transforms and sends your data to TikTok Events API:
1. **Real-time sync**: Events are sent as they occur in Masivo
2. **Smart transformation**: Events are automatically transformed to TikTok's format
3. **Event mapping**: Masivo events are mapped to TikTok standard events
4. **Data validation**: Configuration and data are validated before sending
5. **Error handling**: Failed syncs are logged for debugging
### Automatic transformations
Masivo automatically handles several transformations to ensure compatibility:
* **Event names**: Masivo events mapped to TikTok standard events
* **Field mapping**: Common field names are mapped automatically
* **Contents array**: Products/items are transformed to TikTok's `contents` structure
* **Parameter flattening**: Custom events flatten nested objects into flat key-value pairs
* **Timestamp conversion**: Converts timestamps to TikTok's Unix timestamp format
### Event mapping
Masivo automatically maps common e-commerce events to TikTok standards:
| Your event | TikTok event |
| - | - |
| `purchase` | `CompletePayment` |
| `PURCHASE` | `CompletePayment` |
| `add_to_cart` | `AddToCart` |
| `begin_checkout` | `InitiateCheckout` |
| `add_payment_info` | `AddPaymentInfo` |
| `view_item` | `ViewContent` |
| `search` | `Search` |
| `sign_up` | `CompleteRegistration` |
## Data structure
### Event format for purchases
Masivo's native purchase format (with `order` object) is automatically transformed for TikTok:
```json theme={null}
{
"customer_id": "cust_123",
"brand_id": null,
"type": "PURCHASE",
"order": {
"purchase_id": "abc123",
"value": 300,
"discounted_value": 300,
"products": [
{
"sku": "SKU-001",
"name": "Running Shoes",
"amount": 1,
"value": 129.99
}
],
"payment_method": "CREDIT"
},
"fulfilled": true,
"issued_at": "2024-10-17T10:30:00Z"
}
```
**Automatic transformation**:
* `order.products` → `contents` array for TikTok
* `order.value` → `value`
* `order.purchase_id` → `transaction_id`
* `order.currency` → `currency` (if present)
* `sku` → `content_id`
* `name` → `content_name`
* `amount` → `quantity`
### Contents array structure
For conversion events, each item in the `contents` array supports these TikTok fields:
```json theme={null}
{
"content_id": "SKU-001", // Required: Product ID
"content_name": "Running Shoes", // Required: Product name
"content_type": "product", // Content type (product, product_group)
"content_category": "Footwear", // Product category
"brand": "Nike", // Brand name
"price": 129.99, // Unit price
"quantity": 1 // Quantity
}
```
At minimum, each content item must have either `content_id` or `content_name`.
All other fields are optional but recommended for better ad optimization.
## Field mapping
Masivo automatically maps common alternative field names to TikTok standards:
| Your field | Maps to TikTok | Context |
| - | - | - |
| `customer_id` | `external_id` | Event level |
| `id` | `content_id` | In items/contents |
| `sku` | `content_id` | In products |
| `name` | `content_name` | In items/contents |
| `item_name` | `content_name` | In items/contents |
| `unit_price` | `price` | In items/contents |
| `amount` | `quantity` | In products |
| `order.products` | `contents` | In PURCHASE events |
| `order.value` | `value` | In PURCHASE events |
| `order.purchase_id` | `transaction_id` | In PURCHASE events |
| `order_id` | `transaction_id` | In purchase events |
| `query` | `query` | In search events |
## Examples
### Purchase event (Masivo native format)
```json theme={null}
{
"customer_id": "cust_123",
"type": "PURCHASE",
"order": {
"purchase_id": "order_789",
"value": 300,
"discounted_value": 275,
"products": [
{
"sku": "SKU-001",
"name": "Running Shoes",
"amount": 1,
"value": 129.99,
"brand": "Nike"
},
{
"sku": "SKU-002",
"name": "Sports Socks",
"amount": 2,
"value": 15.99,
"brand": "Adidas"
}
],
"payment_method": "CREDIT"
},
"fulfilled": true,
"issued_at": "2024-10-17T10:30:00Z"
}
```
### Purchase event (Alternative format)
```json theme={null}
{
"type": "purchase",
"customer_id": "cust_123",
"currency": "USD",
"value": 159.99,
"transaction_id": "ORD-789",
"items": [
{
"content_id": "SKU-001",
"content_name": "Running Shoes",
"brand": "Nike",
"price": 129.99,
"quantity": 1
}
]
}
```
### Add to cart event
```json theme={null}
{
"type": "add_to_cart",
"customer_id": "cust_123",
"currency": "USD",
"value": 49.99,
"items": [
{
"content_id": "PROD-456",
"content_name": "Wireless Headphones",
"brand": "Sony",
"content_category": "Electronics",
"price": 49.99,
"quantity": 1
}
]
}
```
### View content event
```json theme={null}
{
"type": "view_item",
"customer_id": "cust_123",
"content_type": "product",
"value": 79.99,
"currency": "USD",
"items": [
{
"content_id": "PROD-789",
"content_name": "Smart Watch",
"brand": "Apple",
"price": 79.99
}
]
}
```
### Search event
```json theme={null}
{
"type": "search",
"customer_id": "cust_123",
"query": "wireless headphones"
}
```
### Button click event (Use exact TikTok name)
```json theme={null}
{
"type": "ClickButton",
"customer_id": "cust_123",
"description": "Homepage CTA button clicked"
}
```
### Form submission event (Use exact TikTok name)
```json theme={null}
{
"type": "SubmitForm",
"customer_id": "cust_123",
"description": "Contact form submitted",
"form_name": "Contact Us"
}
```
### Truly custom event (not in TikTok catalog)
```json theme={null}
{
"type": "free_trial_started",
"customer_id": "cust_123",
"plan_name": "Premium",
"trial_days": 14
}
```
This becomes a custom event with parameters: `custom_plan_name`, `custom_trial_days`.
**When to use each approach:** - Use **Auto-mapped events** (`purchase`,
`add_to_cart`) for convenience - Use **Exact TikTok names** (`ClickButton`,
`Subscribe`) when you need those specific TikTok events - Use **Custom
events** (`free_trial_started`) for tracking business-specific actions not in
TikTok's catalog
## Audience synchronization
### Audience calculation operations
When audiences are calculated in Masivo, they're sent to TikTok Events API as custom events:
* Event name: `AudienceUpdated`
* Includes: `audience_id`, `audience_name`, `customers_count`, `action`
* Customer IDs are included for reference
### Audience DELETE operations
When audiences are deleted:
* Event name: `AudienceRemoved`
* Automatically includes all customers in the audience
* Ensures proper cleanup in your TikTok tracking
Audience events use a special `external_id` of "audience\_manager" and include
all relevant customer information for proper tracking.
## Validation
Before sending data to TikTok Events API, Masivo validates:
* **API credentials**: Pixel Code and Access Token are configured correctly
* **Event configuration**: The event type is enabled in your settings
* **Data completeness**: Required fields are present for each event type
* **Purchase status**: Only fulfilled purchases are sent (if configured)
## Best practices
1. **Always include customer\_id**: Required for user identification (automatically mapped to TikTok's `external_id`)
2. **Use consistent content IDs**: Ensures accurate product tracking and optimization
3. **Include all content fields**: Richer data enables better ad targeting
4. **Add currency codes**: Required for accurate revenue and ROAS tracking
5. **Send timestamps**: Use `issued_at` for accurate event timing
6. **Include value for conversions**: Essential for conversion optimization and bidding
7. **Use content\_type**: Helps TikTok understand your catalog structure
## TikTok Pixel vs Events API
### Why use Events API?
* **Server-side tracking**: No reliance on browser cookies or client-side tracking
* **Better data accuracy**: Bypass ad blockers and browser restrictions
* **Complete data**: Capture 100% of conversions, not just trackable ones
* **Privacy compliant**: More control over data sharing and user privacy
* **Deduplication**: TikTok automatically deduplicates with Pixel events
Use both TikTok Pixel (client-side) and Events API (server-side) together for
the most comprehensive tracking. TikTok will automatically deduplicate events
using the `event_id` parameter.
## Conversion optimization
TikTok uses your conversion events to optimize ad delivery:
* **CompletePayment**: Optimize for purchases and revenue
* **InitiateCheckout**: Target users likely to start checkout
* **AddToCart**: Reach users who add products to cart
* **ViewContent**: Drive product page views
* **CompleteRegistration**: Optimize for new user signups
For best results, send at least 50 conversion events per week per conversion
event type. This gives TikTok enough data to optimize your campaigns.
## Advanced features
### User identification
TikTok matches events to users using:
1. **external\_id**: Your customer ID (highest priority)
2. **email**: SHA256 hashed email address
3. **phone**: SHA256 hashed phone number
4. **TikTok click ID**: From ad clicks (ttclid parameter)
Masivo automatically sends the `external_id` (customer\_id) for all events.
### Value optimization
For value-based campaigns, always include:
* `value`: Transaction or conversion value
* `currency`: ISO 4217 currency code (USD, EUR, etc.)
* `contents`: Product details for better optimization
### Event deduplication
To prevent duplicate events when using both Pixel and Events API:
1. Supply an `event_id` shared by the browser and server copy of the same event.
2. Masivo forwards `event_id`, falling back to the Masivo event `id`.
3. Masivo preserves `issued_at` as `event_time`; a timestamp is not a replacement for a shared event ID.
## Limitations
* **Contents array**: Transformed from items/products array structure
* **Nested objects**: Custom events flatten nested objects into `custom_prefix_key` format
* **Data types**: Only primitives (string, number, boolean) are supported
* **Rate limits**: Subject to TikTok Events API rate limits (typically 1000 events/second)
* **Event delay**: Events can be sent up to 7 days after they occurred
Events older than 7 days will be rejected by TikTok. Ensure your integration
is syncing regularly to avoid data loss.
## Troubleshooting
### Events not appearing in TikTok
* Check that your Pixel Code starts with "C9"
* Verify your Access Token is valid and not expired
* Ensure the event type is enabled in Masivo settings
* Check that `customer_id` is included in your events
* Verify your Pixel is active in TikTok Ads Manager
### Purchase events missing
* Verify that "Purchases" is enabled in settings
* Confirm purchases are marked as fulfilled
* Check that `transaction_id` or `order_id` is unique for each purchase
* Ensure `contents` array contains at least one item with `content_id` or `content_name`
* Verify `value` and `currency` are included
### Low conversion attribution
* Ensure you're sending `customer_id` for all events
* Check that events are being sent within 7 days
* Verify Pixel Code is correctly installed on your website
* Forward actual captured `ttclid` and `_ttp` values as `attribution.ttclid` and `attribution.ttp`; also supply `event_source_url`.
### API errors
* **401 Unauthorized**: Check your Access Token is valid
* **400 Bad Request**: Verify your Pixel Code format (should start with C9)
* **403 Forbidden**: Ensure your Access Token has proper permissions
* **429 Too Many Requests**: You're hitting rate limits, contact support
TikTok Events API has real-time processing. Events should appear in your
Events Manager within a few minutes. Check the "Test Events" tool to verify
your integration.
## Testing your integration
To test your integration before going live:
1. Use the **Test Events** tool in TikTok Events Manager
2. Send test events through Masivo
3. Verify events appear in Events Manager within a few minutes
4. Check that event parameters are correct
5. Confirm conversion values are accurate
Use TikTok's Test Events tool (**Events Manager → Overview → Test Events**) to
see events in real-time and verify the data structure before sending
production traffic.
## Getting Access Token
### Method 1: Through TikTok Ads Manager (Recommended)
1. Go to [TikTok Ads Manager](https://ads.tiktok.com)
2. Navigate to **Tools → Events → Web Events**
3. Select your Pixel
4. Click on **Settings**
5. Scroll to **Events API** section
6. Click **Generate Access Token**
7. Copy the token immediately (it won't be shown again)
### Method 2: Through TikTok for Business
1. Go to [TikTok for Business](https://business-api.tiktok.com/)
2. Create a developer account if you haven't
3. Create an app in the Developer Portal
4. Request access to Events API
5. Generate long-term access token
6. Copy the token for use in Masivo
Access Tokens are sensitive credentials. Store them securely and never share
them publicly. If compromised, regenerate immediately in TikTok Ads Manager.
## Support and resources
* [TikTok Events API Documentation](https://business-api.tiktok.com/portal/docs?id=1741601162187777)
* [TikTok Ads Manager](https://ads.tiktok.com)
* [TikTok Events Manager](https://ads.tiktok.com/i18n/events_manager)
* [TikTok Conversion Events](https://ads.tiktok.com/help/article?aid=10028)
Need help? Contact Masivo support with your Pixel Code (but never your Access
Token) and we'll help you troubleshoot your integration.
# Masivo
Source: https://docs.masivo.ai/overview
Welcome to Masivo's documentation!
## What is Masivo?
Masivo is a loyalty platform designed to help you understand your customers' needs and preferences. We recognize that **every customer is unique**, so we believe every interaction should be tailored to their individual needs.
At Masivo, we strongly believe that loyalty programs are **not just about offering rewards** in the hope that customers will return, but about building lasting relationships with them.
An **increase in RFM** (Recency, Frequency, Monetary value) is not achieved by chance. It is the direct result of a well-executed loyalty program. That's why we created Masivo, a service that helps you craft strategies that **truly convert**.
## Main Features
| Feature | Description |
| - | - |
| [Tier system](/concepts/tiers) | We offer a tier system that allows you to create different levels of rewards for your customers. |
| [Campaigns](/concepts/campaigns) | Simplify your distribution of rewards. Create powerful rules to decide who gets what and when. |
| [Rewards](/concepts/rewards) | Masivo comes with a variety of rewards built-in, including points, discounts, free products, and more. |
| [E-commerce friendly](/api-reference) | We help you integrate your loyalty program with your e-commerce platform. |
## Looking for something specific?
Check our [list](/case) of some of the most common problems Masivo is solving today.
Understand your customers' needs and preferences
Learn how to increase Recency, Frequency, and Monetary value
## Setting up
To start your journey with Masivo, please choose the path that best fits your needs.
Dive into the Masivo platform and explore its features.
Learn how to integrate Masivo with your e-commerce platforms
Let's create your account and configure your loyalty program
Explore our API reference to integrate Masivo with your platform
# Quickstart
Source: https://docs.masivo.ai/quickstart
Configure your loyalty program in under 5 minutes
## Setup your account
If you haven't already, sign up for a Masivo account to get started. You can create an account on our [dashboard](https://app.masivo.ai).
### Configure your loyalty program
You can configure rules that your customers must follow to enter or exit
each of your tiers. Learn more about [creating
tiers](/guides/setup-a-tier-system).
There are many types of rewards you can offer your customers. Each type has
their own rules and requirements. Learn more about [creating
rewards](/guides/create-rewards).
Distribute rewards using coupons, purchases and other customer events. Learn
more about [launching a campaign](/guides/launch-a-campaign).
### Give it a test run
The playground is a user friendly interface that allows you to emulate your
customers' interactions with your loyalty program. You can learn more about
the [playground](/guides/masivo-playground) here.
The API reference comes with an interactive interface that allows you to
emulate your customers' interactions with your loyalty program. You can
learn more about the [API reference](/api-reference) here.
## Next steps
Now that you've set up a simple loyalty program, here are some additional features you can explore to power up your system.
Enhance your campaign rules to reward customers based on certain conditions
Track your customers' growth and engagement
Monitor your loyalty program's performance
Learn how to integrate Masivo with your e-commerce platforms
# Fulfill event
Source: https://docs.masivo.ai/api-reference/behavior/events/[eventId]/fulfill/patch
PATCH /behavior/events/{eventId}/fulfill
Fulfill a behavior event. This will redeem the event rewards and offer campaign rewards to the customer.
# Reverse event
Source: https://docs.masivo.ai/api-reference/behavior/events/[eventId]/reverse/delete
POST /behavior/events/{eventId}/reverse
Reverse the given event. This will also undo the redemptions and accumulations made on the event.
# Emit event
Source: https://docs.masivo.ai/api-reference/behavior/events/post
POST /behavior/events
Create a behavior event. This triggers matching campaigns to offer rewards to the customer. Store, channel, and product scope is evaluated against the event. Tier scope is evaluated using the customer's tier at the start of event processing, before any tier transition produced by the event. null or an empty tiers array means no tier restriction.
# Get campaign
Source: https://docs.masivo.ai/api-reference/campaigns/[id]/get
GET /campaigns/{id}
Get a specific campaign
# Update campaign
Source: https://docs.masivo.ai/api-reference/campaigns/[id]/put
PUT /campaigns/{id}
Update a campaign with a full payload. Requires a SERVER API token. The account_id is derived from the access token and must not be sent in the request body. The type query parameter must match the existing campaign type. Updates reject missing campaigns, invalid payloads, and blocked sensitive changes. Ended campaigns cannot be updated. Started campaigns cannot update start_date, attributes, rules, or brand_id, and end_date cannot be moved to the past.
# Get campaigns
Source: https://docs.masivo.ai/api-reference/campaigns/get
GET /campaigns
Get all campaigns
# Create a new campaign
Source: https://docs.masivo.ai/api-reference/campaigns/post
POST /campaigns
Create a campaign. Requires a SERVER API token. The account_id is derived from the access token and must not be sent in the request body. The selected campaign type is sent in the type query parameter and determines the body validation. Campaign rules must include at least one main/referrer rule. Every rule effect requires a reward_id referencing a reward that already exists and is ACTIVE for the account; rewards are created from the dashboard, not through this API, so create the reward first and reuse its id here. Create rejects past start dates, start dates one year or more in the future, end dates before start_date, and end dates one year or more after start_date.
# Get communication thread
Source: https://docs.masivo.ai/api-reference/communication/threads/[id]/get
GET /communication/threads/{id}
Get a single communication thread shell by id.
# List communication messages
Source: https://docs.masivo.ai/api-reference/communication/threads/[id]/messages/get
GET /communication/threads/{id}/messages
List chronological messages for a communication thread.
# Create communication message
Source: https://docs.masivo.ai/api-reference/communication/threads/[id]/messages/post
POST /communication/threads/{id}/messages
Create a message, reply, internal note, system event, or review event inside a communication thread. Use parent_message_id for a reply to a specific message.
# List communication threads
Source: https://docs.masivo.ai/api-reference/communication/threads/get
GET /communication/threads
List communication thread shells for tickets, reviews, customers, or channels. This endpoint returns latest-message summary fields only; fetch messages from the messages endpoint.
# Create communication thread
Source: https://docs.masivo.ai/api-reference/communication/threads/post
POST /communication/threads
Create a communication thread attached to a ticket, review, customer, or external provider thread.
# Delete contact by ID
Source: https://docs.masivo.ai/api-reference/contacts/[id]/delete
DELETE /contacts/{id}
Delete a contact by its unique identifier (UID). This performs a soft delete by setting the deleted_at timestamp.
# Update contact by ID
Source: https://docs.masivo.ai/api-reference/contacts/[id]/put
PUT /contacts/{id}
Update a contact by its unique identifier (ID). This allows partial updates of contact information.
# Search contacts
Source: https://docs.masivo.ai/api-reference/contacts/get
GET /contacts
Search for contacts using specific criteria. This endpoint allows you to find contacts by their unique identifiers: external ID, email, or document number. At least one of these three fields is required.
# Migrate contacts
Source: https://docs.masivo.ai/api-reference/contacts/migrate/post
POST /contacts/migrate
Migrate contacts in bulk from an external system to Masivo. This endpoint allows you to import up to 10,000 contacts at once. Production accounts can send up to 15 requests every 5 seconds and 10,000 requests per hour to this endpoint.
# Create contact
Source: https://docs.masivo.ai/api-reference/contacts/post
POST /contacts
Create a new contact. This endpoint allows you to create contacts by their unique identifiers: external ID, email, or document number. At least one of these three fields is required.
# Redeem coupon
Source: https://docs.masivo.ai/api-reference/coupons/redeem/post
POST /coupons/redeem
Redeem a coupon code. Store, channel, and product restrictions are evaluated against the request. Tier restrictions are evaluated against the customer's current tier; clients cannot override it in the request. null or an empty tiers array means no tier restriction. A tier mismatch returns: This campaign is not available for this tier.
# Reverse coupon
Source: https://docs.masivo.ai/api-reference/coupons/reverse/post
POST /coupons/reverse
Reverse a coupon redemption and remove rewards from customer's wallet
# Get customer consent
Source: https://docs.masivo.ai/api-reference/customers/[id]/consent/get
GET /customers/{id}/consent
Get customer consent preferences. If consent is null or empty, the API returns default consent values with all purposes set to true.
# Update customer consent
Source: https://docs.masivo.ai/api-reference/customers/[id]/consent/patch
PATCH /customers/{id}/consent
Update customer consent preferences according to IAB TCF standards. Email, push, and WhatsApp audience availability snapshots and enabled-channel counters are synchronized atomically for purposes whose effective value changes.
# Unregister customer
Source: https://docs.masivo.ai/api-reference/customers/[id]/delete
DELETE /customers/{id}
Delete a customer by id
# Customer device token
Source: https://docs.masivo.ai/api-reference/customers/[id]/devices/delete
DELETE /customers/{id}/devices
Unregister customer's device token.
# Customer device token
Source: https://docs.masivo.ai/api-reference/customers/[id]/devices/patch
PATCH /customers/{id}/devices
Update customer's device token.
# Get customer
Source: https://docs.masivo.ai/api-reference/customers/[id]/get
GET /customers/{id}
Get a customer by id
# Identify anonymous customers
Source: https://docs.masivo.ai/api-reference/customers/[id]/identify/anonymous/patch
PATCH /customers/{id}/identify/anonymous
Identify an anonymous customer by providing their real information. This endpoint converts an anonymous customer to a regular customer. The customer must have been created with `is_anonymous: true`. Optionally, you can provide a `new_id` to change the customer's ID during identification.
# Log in-app message event
Source: https://docs.masivo.ai/api-reference/customers/[id]/inapp-messages/[messageId]/events/post
POST /customers/{id}/inapp-messages/{messageId}/events
Record an impression event for an in-app message (shown, clicked, or dismissed).
# List in-app messages
Source: https://docs.masivo.ai/api-reference/customers/[id]/inapp-messages/get
GET /customers/{id}/inapp-messages
List eligible in-app messages for a customer. Returns pending messages that are scheduled, not expired, and within impression limits.
# Log a news feed card event
Source: https://docs.masivo.ai/api-reference/customers/[id]/news-feed/[templateId]/events/post
POST /customers/{id}/news-feed/{templateId}/events
Record that a customer has shown, clicked, or dismissed a news feed card.
# Get customer news feed cards
Source: https://docs.masivo.ai/api-reference/customers/[id]/news-feed/get
GET /customers/{id}/news-feed
List active news feed cards currently eligible for a customer. Cards are resolved from their live templates and ordered by pinned state, priority, and assignment time.
# Update customer
Source: https://docs.masivo.ai/api-reference/customers/[id]/patch
PATCH /customers/{id}
Update a customer by id. Only some fields can be updated using these API.
# Get customer punch-cards
Source: https://docs.masivo.ai/api-reference/customers/[id]/punch-cards/get
GET /customers/{id}/punch-cards
Get punch cards of a customer. By default, only returns ACTIVE punch cards.
# Request referral code
Source: https://docs.masivo.ai/api-reference/customers/[id]/referrals/request-code/post
POST /customers/{id}/referrals/request-code
Request a unique referral code for a customer. If the customer already has a code for this campaign, the existing code will be returned.
# Get customer score streaks
Source: https://docs.masivo.ai/api-reference/customers/[id]/score-streaks/get
GET /customers/{id}/score-streaks
Get score streaks of a customer. By default, only returns ACTIVE streaks. Pass `include=history` to also get a flat, time-ordered list of all history events across all streaks.
# Delete customer tags
Source: https://docs.masivo.ai/api-reference/customers/[id]/tags/delete
DELETE /customers/{id}/tags
You can delete multiple tags from a customer at once.
# Update customer tags
Source: https://docs.masivo.ai/api-reference/customers/[id]/tags/patch
PATCH /customers/{id}/tags
You can upsert multiple tags from a customer at once.
# Customer transactions
Source: https://docs.masivo.ai/api-reference/customers/[id]/transactions/get
GET /customers/{id}/transactions
Get transactions of a customer. By default it returns the transactions of the last 3 months.
# Update customers in bulk
Source: https://docs.masivo.ai/api-reference/customers/bulk/patch
PATCH /customers/bulk
Update customers in bulk. You can update multiple customers at once, each with its own subset of fields (partial update): only the fields included for each customer are written, the rest keep their current values. Available fields are the same as the single customer update endpoint, except devices. Notes: date of birth and gender cannot be changed once set, and anonymous customers must use the identity endpoint.
# Fulfill expired events
Source: https://docs.masivo.ai/api-reference/customers/fulfill_expired_events/post
POST /customers/fulfill_expired_events
Fulfill expired events that were not marked as fulfilled. This endpoint enqueues up to 500 events per call to be processed in the background. Note that events may fail due to expired rewards or insufficient wallet balance at the time of re-processing them. Failed events can be reviewed in the Cron Jobs tab of your account settings to identify the specific event IDs that encountered issues.
# Get customer by document or phone
Source: https://docs.masivo.ai/api-reference/customers/get
GET /customers
Look up a customer by document number, phone, or both within your account. When both are sent, the customer must match both filters. Requires a SERVER API key; CLIENT API keys cannot access this endpoint.
Requires a **SERVER** API key. Pass `document_number`, `phone`, or both. When both are sent, the customer must match both. **CLIENT** keys receive `403`.
# Migrate devices tokens
Source: https://docs.masivo.ai/api-reference/customers/migrate/devices/post
POST /customers/migrate/devices
Migrate device tokens from an old system to Masivo
# Migrate events
Source: https://docs.masivo.ai/api-reference/customers/migrate/events/post
POST /customers/migrate/events
Migrate events from an old system to Masivo
# Migrate customers
Source: https://docs.masivo.ai/api-reference/customers/migrate/post
POST /customers/migrate
Migrate customers from an old system to Masivo. Production accounts can send up to 15 requests every 5 seconds and 10,000 requests per hour to this endpoint.
# Revert migration
Source: https://docs.masivo.ai/api-reference/customers/migrate/revert/delete
DELETE /customers/migrate/revert
Revert all customers migration that don't have new transactions
# Migrate transactions
Source: https://docs.masivo.ai/api-reference/customers/migrate/transactions/post
POST /customers/migrate/transactions
Migrate transactions from an old system to Masivo
# Identify customer
Source: https://docs.masivo.ai/api-reference/events/identify/patch
PATCH /events/identify
Link a customer with a tracking ID to establish analytics relationships.
# Preview order
Source: https://docs.masivo.ai/api-reference/events/redeem/preview/put
PUT /events/redeem/preview
Preview the order as if the given rewards were redeemed. Store, channel, and product restrictions are evaluated against the order. Tier restrictions are evaluated against the customer's current tier; clients cannot override it in the request. null or an empty tiers array means no tier restriction. A reward snapshot already stored in the wallet keeps its saved scope until the wallet update flow replaces it. A tier mismatch returns: This reward is not available for this tier.
# Attribution Tracking
Source: https://docs.masivo.ai/api-reference/guides/attribution-tracking
Preserve UTM, click IDs and matching context from links to events, purchases and connected destinations
## Overview
The same `attribution` contract serves Braze, Meta, TikTok, GA4, Google Ads
conversions, S3 exports and event-derived webhooks. GA4 additionally accepts its
own `ga4` identity/session object. Use the destination matrix below to check what
each integration sends.
Attribution tracking in Masivo enables you to measure and analyze the complete customer journey across all touchpoints. By implementing proper attribution parameters, you can track which marketing campaigns, channels, and touchpoints drive customer behavior, purchases, and engagement. This guide covers the complete attribution system, from generating tracking IDs to analyzing campaign performance.
Attribution data is automatically collected from deeplinks and can be manually
attached to events. All attribution parameters are optional but provide
valuable insights when implemented consistently across your marketing
campaigns.
## Delivery to connected destinations
Send the captured context in `attribution` on each event, including the purchase
at checkout. Single-event and bulk ingestion preserve the same fields. Masivo
does not look up a customer's previous campaign to fill missing purchase UTM;
retaining the context between landing, login, app navigation and checkout is the
client's responsibility. A tracking ID alone does not populate missing UTM.
Non-empty fields at the event's top level take precedence over `attribution`,
then legacy `properties`. Empty values do not erase captured context. The original
nested attribution object remains available in storage and exports.
| Destination | Delivery |
| - | - |
| GA4 | Unprefixed UTM event parameters; `campaign_details` uses native source/medium/campaign fields. Session attribution also requires `ga4` identity and session context. |
| Braze events, including the legacy destination | `properties.utm_*` on custom events and every purchase product. Reversals retain the original purchase context. Existing flattened nested properties remain available. |
| Meta Pixel and App Events | `custom_data.utm_*`; actual `fbc`/`fbp` values go in `user_data`. Meta Pixel prefers the event's `event_source_url` (or `url`) over the destination default. Completed purchases are delivered; pending, cancelled and reversed purchases are not submitted as new conversions. |
| TikTok Events, including the legacy destination | `properties.utm_*` for standard and custom events; `user.ttclid`, `user.ttp`, and `page.url` carry the matching context. |
| Google Ads conversions | Native `gclid`, or `gbraid`, or `wbraid`, in that priority order. UTM alone cannot identify an offline conversion. No arbitrary UTM fields are added to Google's conversion request. |
| S3 JSON / Parquet | The original event data retains its nested `attribution`; Parquet serializes nested objects as JSON strings. |
| Webhooks derived from events | `data.attribution` on reward reservations, cancellations, accumulations, redemptions, event completion/failure and event-driven tier changes. Reversals use the original event context. |
| Audience / remarketing destinations | Membership and customer matching only; they do not accept event UTM attribution. |
Use real client cookies and click IDs. Masivo does not fabricate Meta `fbc` from
UTM or from the purchase timestamp, and does not synthesize a Google or TikTok
click ID. Existing identifier consent filters continue to apply.
Provider references: [Braze event properties](https://www.braze.com/docs/api/objects_filters/event_object),
[TikTok's official Events API mapping](https://github.com/tiktok/gtm-template-eapi/blob/main/template.tpl),
and [Google Ads conversion identifiers and custom variables](https://developers.google.com/google-ads/api/docs/conversions/legacy_oci_guide).
Google Ads custom variables require separately configured conversion-variable IDs
and are not supported with `gbraid`/`wbraid`; this integration does not configure
them automatically.
### Check a complete journey
Use a test customer and configured event destinations in the environment running
these changes. Enable events/purchases and allow the test event type in the
connection filters. `offer_opened` must be an active custom event in your account.
1. Open a Masivo short link with
`?utm_source=braze&utm_medium=email&utm_campaign=qa_attribution&utm_content=button&utm_term=offer`.
Check that the final URL retains those values. For apps, repeat with the app
installed and with deferred resolution after a fresh install.
2. Read the context from that final URL and keep it through login/navigation.
Send `offer_opened` from the SDK with that `attribution`, then call
`await masivo.flush()`. Inspect the request body to confirm the context reaches
Masivo. Flush confirms SDK delivery to Masivo; provider delivery is asynchronous.
3. Make a purchase through your checkout backend. Its
`POST /api/storefront/v1/behavior/events` request must carry the same context.
The backend authenticates with its SERVER-key credentials. CLIENT-key SDKs
cannot send the protected `PURCHASE` event. The following example omits
`issued_at` so the API uses the current time. Replace the customer, SKU, order
ID and currency with your test values, using a new order ID for every run.
```json theme={null}
{
"type": "PURCHASE",
"customer_id": "YOUR_TEST_CUSTOMER",
"brand_id": null,
"fulfilled": true,
"currency": "USD",
"order": {
"purchase_id": "qa-attribution-order-001",
"value": 10,
"payment_method": "CASH",
"products": [
{ "sku": "YOUR_TEST_SKU", "amount": 1, "value": 10 }
]
},
"attribution": {
"utm_source": "braze",
"utm_medium": "email",
"utm_campaign": "qa_attribution",
"utm_content": "button",
"utm_term": "offer"
}
}
```
4. For GA4, include the real `ga4` identity/session object separately on both
events. For paid-media matching, use a real ad click and forward the captured
provider identifiers/cookies. Synthetic values test transport, not paid-media
attribution. Google Ads conversions without a native click ID are skipped.
5. After the integration workers run, inspect the custom event and purchase in
each destination. In Braze, check event and purchase properties; in Meta and
TikTok, inspect the event payload; in GA4, inspect the event parameters (use
`debug_mode: true` for a debug session). Compare against the destination matrix
above: `utm_campaign` must remain `qa_attribution`. Check integration errors
if a payload does not arrive. A successful connection test alone does not
establish that this purchase was delivered.
6. Inspect an S3 export after its configured export job and a webhook generated
by that event. Webhooks for rewards or tier changes require that the purchase
actually triggers those actions. Both should retain the context in the matrix.
7. Reverse the test purchase using
`POST /api/storefront/v1/behavior/events/{eventId}/reverse` with
`{ "reason": "Attribution QA" }`, using the Masivo event ID returned for the
purchase. Check the GA4 `refund`, Braze reversal purchase properties, and any
resulting webhooks for the original UTM. Meta must not receive the reversal
as another `Purchase` conversion.
The transport check passes when the captured context reaches the expected fields
without changing source/campaign between the click and purchase. Paid-media
credit and acquisition reports also depend on the provider's matching and
attribution settings. Sending UTM properties to Braze does not itself configure
Braze's revenue attribution windows.
## Understanding Attribution Parameters
### Attribution Data Structure
Attribution data in Masivo consists of several key parameters that help track the customer journey:
```typescript theme={null}
interface AttributionData {
tracking_id?: string; // Unique identifier for tracking user sessions
utm_source?: string; // Traffic source (e.g., "google", "facebook")
utm_medium?: string; // Marketing medium (e.g., "cpc", "email", "social")
utm_campaign?: string; // Campaign name (e.g., "summer-sale-2024")
utm_term?: string; // Paid search keywords
utm_content?: string; // Ad variation or email version
utm_referral?: string; // Specific referral source
gclid?: string;
gbraid?: string;
wbraid?: string;
fbclid?: string;
ttclid?: string;
fbc?: string; // Actual Meta _fbc cookie
fbp?: string; // Actual Meta _fbp cookie
ttp?: string; // Actual TikTok _ttp cookie
event_source_url?: string; // URL where this event occurred
}
```
### How Attribution Works in Masivo
Attribution data flows through the system in several ways:
1. **Automatic Collection**: Generated automatically when customers click deeplinks
2. **Event Attribution**: Manually attached to events via the API
3. **Purchase Handoff**: Your application retains the context and forwards it to the checkout backend
4. **Analytics**: Aggregated for campaign performance analysis
## Campaign links
Masivo preserves `utm_source`, `utm_medium`, `utm_campaign`, `utm_term`, and
`utm_content`. Non-empty incoming values override the stored destination's
values. Missing destination fields first use the selected fallback URL,
including campaign fields inside a Play Store `referrer`. Only fields still
missing on internal campaign links receive these defaults:
| Channel | Source | Medium |
| - | - | - |
| Email | `masivo` | `email` |
| Push | `masivo` | `push` |
| WhatsApp | `masivo` | `whatsapp` |
The default campaign is the link's `campaign_id`, or `template_id` if there is
no campaign. Explicit values remain unchanged. In-app/news-feed navigation
and external links do not receive automatic acquisition labels. Existing
links use these rules when clicked, without rewriting stored links.
For example, a Braze email should explicitly carry `utm_source=braze` and
`utm_medium=email`. A Meta ad can use `utm_source=facebook` and
`utm_medium=paid_social`, with its campaign in `utm_campaign`. Masivo preserves
these labels rather than replacing the source with the name of the shortener.
`fbclid` alone does not provide a manually tagged GA4 campaign.
The same UTM context travels through the direct destination, HTTPS fallback,
Android Intent and installed-app resolution (`$app-found=true`). Google Play
receives UTM inside its encoded `referrer`, alongside Masivo's `tracking_id`
and `link`. Existing referrer fields are preserved. Editing UTM in the dashboard
also preserves unrelated query parameters and URL fragments.
## 1. Tracking ID Generation and Usage
### Understanding Tracking IDs
The `tracking_id` is a unique identifier that connects customer actions across your marketing funnel. It's automatically generated for deeplinks and can be manually created for custom tracking scenarios.
### Automatic Tracking ID Generation
When customers click on a Masivo deeplink, a `tracking_id` is automatically generated:
```typescript theme={null}
// This happens automatically in Masivo's deeplink handler
import { v4 as uuidv4 } from "uuid";
const tracking_id = uuidv4(); // e.g., "f47ac10b-58cc-4372-a567-0e02b2c3d479"
// Added to attribution data
const attribution = {
tracking_id,
utm_source: "email",
utm_medium: "newsletter",
utm_campaign: "spring-collection"
};
// Appended to destination URL
const destinationUrl =
"https://yourapp.com/products?$tracking_id=f47ac10b-58cc-4372-a567-0e02b2c3d479";
```
### Extracting Tracking ID from URLs
In your application, extract the `tracking_id` from URL parameters to maintain attribution throughout the customer journey:
```typescript theme={null}
// Mobile app or web application
const extractTrackingId = (url: string): string | null => {
try {
const urlObj = new URL(url);
return urlObj.searchParams.get("$tracking_id");
} catch (error) {
console.error("Invalid URL:", error);
return null;
}
};
// Usage examples
const tracking_id = extractTrackingId(window.location.href);
// Or from deep link parameters in mobile apps
const tracking_id = extractTrackingId(incomingDeepLink);
// Store for later use in events
if (tracking_id) {
// Store in session storage, app state, or local storage
sessionStorage.setItem("masivo_tracking_id", tracking_id);
}
```
### Using Tracking ID in Events
Once you have the `tracking_id`, include it in attribution data when sending events:
```typescript theme={null}
const sendEventWithAttribution = async (eventData: {
type: string;
customer_id: string;
brand_id: string | null;
[key: string]: any;
}) => {
// Retrieve stored tracking ID
const tracking_id = sessionStorage.getItem("masivo_tracking_id");
const payload = {
...eventData,
attribution: {
tracking_id,
// Add other attribution parameters as needed
utm_source: "mobile_app",
utm_medium: "push_notification"
}
};
const response = await fetch(
"https://app.masivo.ai/api/storefront/v1/behavior/events",
{
method: "POST",
headers: {
Authorization: `Bearer ${accessToken}`,
"Content-Type": "application/json"
},
body: JSON.stringify(payload)
}
);
return response.json();
};
// Example: Track purchase with attribution
await sendEventWithAttribution({
type: "PURCHASE",
customer_id: "customer-123",
brand_id: "brand-456",
order: {
purchase_id: "order-789",
value: 99.99,
products: [
{
sku: "PROD-001",
amount: 1,
value: 99.99
}
],
payment_method: "CREDIT"
}
});
```
## 2. UTM Parameters and Campaign Tracking
### Understanding UTM Parameters
UTM parameters are standardized tags that help track the effectiveness of marketing campaigns:
#### utm\_source
Identifies the traffic source:
* `"google"` - Google search or ads
* `"facebook"` - Facebook ads or posts
* `"email"` - Email campaigns
* `"newsletter"` - Newsletter campaigns
* `"organic"` - Organic/direct traffic
#### utm\_medium
Describes the marketing medium:
* `"cpc"` - Cost-per-click advertising
* `"email"` - Email marketing
* `"social"` - Social media
* `"push"` - Push notifications
* `"sms"` - SMS campaigns
#### utm\_campaign
Names the specific campaign:
* `"summer-sale-2024"`
* `"new-user-onboarding"`
* `"cart-abandonment-recovery"`
#### utm\_term
Used for paid search keywords:
* `"premium headphones"`
* `"wireless earbuds"`
#### utm\_content
Differentiates ad variations:
* `"banner-ad-v1"`
* `"email-button-cta"`
* `"video-ad-30s"`
### Implementing UTM Tracking
```typescript theme={null}
const sendPurchaseWithUTMTracking = async (
customerId: string,
orderData: any,
campaignInfo: {
source: string;
medium: string;
campaign: string;
term?: string;
content?: string;
}
) => {
const attribution = {
tracking_id: sessionStorage.getItem("masivo_tracking_id"),
utm_source: campaignInfo.source,
utm_medium: campaignInfo.medium,
utm_campaign: campaignInfo.campaign,
utm_term: campaignInfo.term,
utm_content: campaignInfo.content,
revenue: orderData.total
};
const eventData = {
type: "PURCHASE",
customer_id: customerId,
brand_id: orderData.brand_id,
attribution,
order: {
purchase_id: orderData.id,
value: orderData.total,
products: orderData.items.map(item => ({
sku: item.sku,
amount: item.quantity,
value: item.price
})),
payment_method: orderData.payment_method
}
};
return await fetch(
"https://app.masivo.ai/api/storefront/v1/behavior/events",
{
method: "POST",
headers: {
Authorization: `Bearer ${accessToken}`,
"Content-Type": "application/json"
},
body: JSON.stringify(eventData)
}
);
};
// Usage example
await sendPurchaseWithUTMTracking("customer-123", orderData, {
source: "google",
medium: "social",
campaign: "spring-collection-launch",
content: "carousel-ad-variant-a"
});
```
## 3. Revenue and Cost Attribution
### Tracking Revenue Attribution
Revenue attribution helps measure the financial impact of marketing campaigns and track the revenue and cost at each step of the customer journey until completion, providing a complete view of campaign ROI:
```typescript theme={null}
const trackRevenueAttribution = async (
customerId: string,
purchaseData: {
order_value: number;
acquisition_cost: number;
campaign_source: string;
}
) => {
const attribution = {
tracking_id: sessionStorage.getItem("masivo_tracking_id"),
revenue: purchaseData.order_value,
cost: purchaseData.acquisition_cost,
utm_source: purchaseData.campaign_source,
utm_medium: "paid_advertising"
};
const eventData = {
type: "PURCHASE",
customer_id: customerId,
brand_id: "your-brand-id",
attribution,
order: {
purchase_id: `order-${Date.now()}`,
value: purchaseData.order_value,
products: [
/* order products */
],
payment_method: "CREDIT"
}
};
return await sendEventWithAttribution(eventData);
};
```
### Cost Tracking for Campaign Analysis
Track marketing costs to calculate ROI:
```typescript theme={null}
const trackCampaignCost = async (
customerId: string,
action: string,
campaignCost: number
) => {
const attribution = {
tracking_id: sessionStorage.getItem("masivo_tracking_id"),
cost: campaignCost,
utm_source: "google_ads",
utm_medium: "cpc",
utm_campaign: "holiday-sale-2024"
};
const eventData = {
type: action, // e.g., "ADD_TO_CART", "VIEW_PRODUCT"
customer_id: customerId,
brand_id: "your-brand-id",
attribution
};
return await sendEventWithAttribution(eventData);
};
```
For more information about implementing campaigns and measuring their effectiveness, see the [Journey Triggers Guide](/api-reference/guides/journey-triggers) and [Customer Audience Enrollment Guide](/api-reference/guides/customer-audience-enrollment).
## Google Analytics 4 attribution
The `attribution` object records Masivo tracking and UTM context. GA4 session
attribution additionally requires the real Google tag/Firebase identity and
session, supplied separately in the event's optional `ga4` object.
Masivo short links preserve incoming UTM through destination, app resolution
and fallback URLs. Google Play receives UTM inside `referrer` with Masivo's
tracking ID. Non-empty incoming UTM override the destination configuration.
Internal email, push and WhatsApp links fill absent source/medium with
`masivo` and `email`, `push` or `whatsapp`; external links and internal
in-app/news-feed navigation do not receive an automatic acquisition source.
Keep explicit Braze/Meta labels when using their campaigns. A Masivo short
URL or a `tracking_id` alone does not identify a Google Analytics session.
See [Google Analytics integration](/integrations/google-analytics) for the
web/app contract, legacy fallback and end-to-end verification steps.
# Customer Audience Enrollment
Source: https://docs.masivo.ai/api-reference/guides/customer-audience-enrollment
Complete guide for managing customer tags and audience enrollment in Masivo - from tag assignment to automatic audience segmentation
## Overview
Customer audience enrollment in Masivo is powered by a sophisticated tagging and segmentation system. By assigning tags to customers, you enable automatic enrollment into dynamic audiences based on conditions and customer behavior. This guide covers the complete workflow from tag management to audience segmentation.
Customer tags are key-value pairs stored in the customer's `tags` field. These
tags are used by audience conditions to automatically segment customers into
targeted groups for marketing campaigns.
## Understanding Customer Tags and Audiences
### Customer Tags System
Customer tags in Masivo are flexible key-value pairs that can store:
* **String values**: `"vip_status": "gold"`
* **Number values**: `"lifetime_purchases": 15`
* **Boolean values**: `"newsletter_subscriber": true`
### Audience Enrollment Process
When you assign tags to customers, Masivo automatically:
1. **Evaluates Audience Conditions**: Checks all active audiences for matching conditions
2. **Enrolls Customers**: Adds qualifying customers to the `customers_by_audience` table
3. **Subscribes to FCM Topics**: Automatically subscribes device tokens to audience-specific topics
4. **Enables Targeting**: Makes customers available for audience-specific campaigns and journeys
## 1. Managing Customer Tags
### Assign Tags to Customers
Use the [`PATCH /customers/{id}/tags`](/api-reference/customers/\[id]/tags/patch) endpoint to assign or update customer tags:
```typescript theme={null}
const customerId = "your-customer-id";
const customerTags = {
vip_status: "gold",
lifetime_purchases: 15,
newsletter_subscriber: true,
last_purchase_category: "electronics",
preferred_language: "en"
};
const response = await fetch(
`https://app.masivo.ai/api/storefront/v1/customers/${customerId}/tags`,
{
method: "PATCH",
headers: {
Authorization: `Bearer ${accessToken}`,
"Content-Type": "application/json",
"x-account-id": "your-account-id"
},
body: JSON.stringify({ tags: customerTags })
}
);
if (response.ok) {
const result = await response.json();
console.log("Customer tags updated:", result.data.customer.tags);
} else {
const error = await response.json();
console.error("Tag assignment failed:", error.details);
}
```
### Tag Assignment Behavior
When assigning tags, Masivo:
* **Merges Tags**: New tags are merged with existing ones using `{ ...existingTags, ...newTags }`
* **Overwrites Values**: If a tag key already exists, the new value overwrites the old one
* **Preserves Other Tags**: Existing tags not included in the request remain unchanged
### Remove Customer Tags
Use the [`DELETE /customers/{id}/tags`](/api-reference/customers/\[id]/tags/delete) endpoint to remove specific tags:
```typescript theme={null}
const removeCustomerTags = async (
customerId: string,
tagsToRemove: string[]
) => {
const tagsParam = tagsToRemove.join(",");
const response = await fetch(
`https://app.masivo.ai/api/storefront/v1/customers/${customerId}/tags?tags=${encodeURIComponent(tagsParam)}`,
{
method: "DELETE",
headers: {
Authorization: `Bearer ${accessToken}`,
"Content-Type": "application/json"
}
}
);
if (response.ok) {
const result = await response.json();
console.log("Tags removed successfully:", result.data.customer.tags);
} else {
const error = await response.json();
console.error("Tag removal failed:", error.details);
}
};
// Remove specific tags
await removeCustomerTags("customer-123", ["old_segment", "temporary_flag"]);
```
## 2. Audience Segmentation with Tags
### How Audience Conditions Work
Masivo audiences use customer tags for segmentation through condition matching. The system supports various condition types:
#### String Tag Conditions
```typescript theme={null}
// Example: Target customers with VIP status
const condition = {
type: "Customer includes tag",
primitive: "string",
tag: "vip_status_tag_id", // Reference to customer tag definition
operator: "equals",
value: "gold"
};
```
#### Number Tag Conditions
```typescript theme={null}
// Example: Target customers with high purchase count
const condition = {
type: "Customer includes tag",
primitive: "number",
tag: "lifetime_purchases_tag_id",
operator: "greater_than",
value: 10
};
```
#### Boolean Tag Conditions
```typescript theme={null}
// Example: Target newsletter subscribers
const condition = {
type: "Customer includes tag",
primitive: "boolean",
tag: "newsletter_subscriber_tag_id",
operator: "equals",
value: true
};
```
### Automatic Audience Enrollment
When you assign tags to customers, Masivo automatically:
1. **Processes Audience Conditions**: Evaluates all active audiences against the customer's updated tags
2. **Enrolls in Matching Audiences**: Adds customer to `customers_by_audience` table for qualifying audiences
3. **FCM Topic Subscription**: Subscribes customer's device tokens to audience-specific topics (`{account_id}_audience_{audience_id}`)
4. **Real-time Updates**: Processes enrollment in background queues for scalability
### Tag-Based Segmentation Examples
#### E-commerce Segmentation
```typescript theme={null}
// Segment high-value customers
const highValueCustomerTags = {
lifetime_value: 1500,
purchase_frequency: "high",
preferred_category: "premium",
vip_member: true
};
// Segment cart abandoners
const cartAbandonerTags = {
cart_status: "abandoned",
last_cart_value: 89.99,
abandonment_count: 3,
email_sent: false
};
```
#### Subscription Business Segmentation
```typescript theme={null}
// Segment trial users
const trialUserTags = {
subscription_status: "trial",
trial_days_remaining: 7,
feature_usage_score: 0.6,
onboarding_completed: true
};
// Segment churned customers
const churnedCustomerTags = {
subscription_status: "cancelled",
churn_reason: "price",
last_login_days_ago: 30,
win_back_eligible: true
};
```
## Conclusion
Customer audience enrollment in Masivo is a powerful system that automatically segments customers based on their tags. By strategically assigning and managing customer tags, you enable sophisticated audience targeting for personalized marketing campaigns and customer journeys.
The automatic enrollment system ensures that customers are always in the right audiences based on their current attributes and behaviors, enabling real-time personalization and targeted messaging through FCM topics and campaign targeting.
For more information about audience creation and management, see the [Create Audiences Guide](/guides/create-audiences).
# FCM Token Management
Source: https://docs.masivo.ai/api-reference/guides/fcm-token-management
Complete guide for managing Firebase Cloud Messaging (FCM) tokens in Masivo - from user registration to token expiration and unregistration
## Overview
Firebase Cloud Messaging (FCM) tokens are essential for delivering push notifications to your customers' devices. This guide covers the complete lifecycle of FCM token management in Masivo, including user registration, migration scenarios, and proper token cleanup when users opt out or tokens expire.
FCM tokens can change periodically due to app updates, device restores, or
Firebase SDK refreshes. Always implement proper token refresh handling in your
application.
## 1. User Registration
### Understanding FCM Token Registration
When registering users for push notifications, you need to associate their FCM token with their customer profile in Masivo. This enables targeted messaging and automatic subscription to audience topics.
### Register a New Device Token
Use the [`PATCH /customers/{id}/devices`](/api-reference/customers/\[id]/devices/patch) endpoint to register or update a customer's device token:
```typescript theme={null}
const customerId = "your-customer-id";
const fcmToken = "duPXivAiS_uR1o2WGFb5fb:APA91bHYGH0sQcTWtJwSPTRB...";
const payload = {
device_token: fcmToken,
device_info: {
platform: "android",
model: "SM-S918B",
osVersion: "34"
}
};
const response = await fetch(
`https://app.masivo.ai/api/storefront/v1/customers/${customerId}/devices`,
{
method: "PATCH",
headers: {
Authorization: `Bearer ${accessToken}`,
"Content-Type": "application/json"
},
body: JSON.stringify(payload)
}
);
if (response.ok) {
const result = await response.json();
console.log("Device registered successfully:", result.message);
// Returns: "FCM token registered successfully"
} else {
const error = await response.json();
console.error("Registration failed:", error.details);
}
```
### What Happens During Registration
When you register a device token, Masivo automatically:
1. **Sets Token Expiration**: Calculates `expires_at` based on your account's `device_token_ttl` setting
2. **Unsubscribes from Previous Topics**: Removes the token from any existing audience subscriptions
3. **Updates Customer Record**: Stores the device token in the customer's `devices` array
4. **Subscribes to Current Audiences**: Automatically subscribes the token to topics for audiences the customer belongs to
5. **Updates Device Token Mapping**: Calls the `update_customer_device_token` stored procedure
### Token Expiration Configuration
Token expiration is controlled by your account's marketing automation settings:
* **device\_token\_ttl**: Number of days before token expires
* **Marketing Automation**: Must be enabled in account settings
To configure token expiration in the dashboard:
1. Go to **Marketing Automation > Settings**
2. Open the **Push** tab
3. In the **Fcm Token** section, set **Token Duration**
4. Click **Save**
For more information, see [Push settings](/guides/settings/push).
If marketing automation is not enabled or `device_token_ttl` is not
configured, the registration will fail with a 400 error: "Marketing automation
is not enabled, enable it in the marketing\_automation settings"
## 2. Token Unregistration and Expiration
### Unregister a Device Token
Use the [`DELETE /customers/{id}/devices`](/api-reference/customers/\[id]/devices/delete) endpoint to remove a device token:
```typescript theme={null}
const unregisterDeviceToken = async (
customerId: string,
deviceToken: string
) => {
const response = await fetch(
`https://app.masivo.ai/api/storefront/v1/customers/${customerId}/devices?device_token=${encodeURIComponent(deviceToken)}`,
{
method: "DELETE",
headers: {
Authorization: `Bearer ${accessToken}`,
"Content-Type": "application/json",
"x-account-id": "your-account-id"
}
}
);
if (response.ok) {
const result = await response.json();
console.log("Token unregistered:", result.message);
// Returns: "FCM token unregistered successfully"
return result;
} else {
const error = await response.json();
throw new Error(`Unregistration failed: ${error.details}`);
}
};
```
### What Happens During Unregistration
When you unregister a device token, Masivo automatically:
1. **Clears Device Array**: Sets the customer's `devices` array to empty `[]`
2. **Unsubscribes from Topics**: Removes the token from all audience topic subscriptions
3. **Updates Device Token Mapping**: Calls the `update_customer_device_token` stored procedure to clean up mappings
### Handling User Opt-out
Implement a user-friendly opt-out flow:
```typescript theme={null}
const handlePushNotificationOptOut = async (customerId: string) => {
try {
// Get current FCM token
const currentToken = await messaging().getToken();
// Unregister from Masivo
await unregisterDeviceToken(customerId, currentToken);
// Update local preferences
await AsyncStorage.setItem("pushNotificationsEnabled", "false");
console.log("Successfully opted out of push notifications");
} catch (error) {
console.error("Error during opt-out:", error);
}
};
```
### Automatic Token Expiration
Masivo automatically handles token expiration based on your configured TTL:
* Tokens are created with an `expires_at` timestamp
* Expired tokens are automatically excluded from push notification targeting
* No manual cleanup is required for expired tokens
### Audience Topic Management
Masivo automatically manages FCM topic subscriptions:
* **Topic Format**: `{account_id}_audience_{audience_id}`
* **Auto-Subscribe**: When registering a token, it's automatically subscribed to topics for audiences the customer belongs to
* **Auto-Unsubscribe**: When unregistering a token, it's removed from all topic subscriptions
## Conclusion
Proper FCM token management in Masivo involves understanding the automatic topic subscription system, token expiration handling, and the relationship between customers and audiences. By following this guide, you'll ensure reliable message delivery while leveraging Masivo's built-in audience segmentation capabilities.
For more information about push notifications in Masivo, see the [Push Notification Integration Guide](/api-reference/guides/integrate-push-notification).
# Integrate deferred deep linking
Source: https://docs.masivo.ai/api-reference/guides/integrate-deferred-deep-linking
Learn how to recover a link's destination after an app install using the Masivo React Native SDK
Deferred deep linking requires `@masivo/rn` version `2.3.0` or later.
### What is deferred deep linking?
When someone taps a Masivo link without your app installed, they're sent to the App Store or Play Store. Deferred deep linking recovers that original destination the first time the app opens after install, so the user lands on the right screen instead of the app's default entry point.
This feature is implemented entirely through `@masivo/rn` — there is no REST endpoint to call directly. The SDK handles device identification and the request to Masivo internally; you only need to wire a few pieces on app startup.
### 1. Provide platform adapters
The SDK does not bundle native modules for reading the Android Install Referrer or the iOS clipboard — you provide them via `setAdapters`:
```typescript theme={null}
client.deepLinks.setAdapters({
getInstallReferrer: () => Application.getInstallReferrerAsync(),
getClipboardText: () => Clipboard.getStringAsync()
});
```
| Adapter | Platform | Purpose | Example library |
| - | - | - | - |
| `getInstallReferrer` | Android | Reads the Play Install Referrer attached to the store redirect | `expo-application` (`getInstallReferrerAsync`) or `react-native-play-install-referrer` |
| `getClipboardText` | iOS | Reads the clipboard on first launch; triggers the iOS 16+ system paste prompt | `expo-clipboard` (`getStringAsync`) |
Both adapters are optional. If you omit one, the SDK simply skips that matching method for that platform and falls back to IP-based fingerprint matching.
### 2. Configure persistent storage
`resolveDeferred` is designed to run once per install: it stores a flag so later app launches skip the request.
```typescript theme={null}
import AsyncStorage from "@react-native-async-storage/async-storage";
import { setAsyncStorage } from "@masivo/rn";
setAsyncStorage(AsyncStorage);
```
If you already call `setDeviceStorage(...)` for secure, Keychain-backed
persistence of your device UUID, call it **after** `setAsyncStorage(...)`.
`setAsyncStorage` wires in-app, device, and deep link storage together;
calling `setDeviceStorage` afterward re-points device storage to your secure
adapter, while deep links and in-app messaging keep using `AsyncStorage`.
Skipping this step doesn't cause incorrect matches — Masivo still resolves each pending click at most once, server-side — but every app launch will call the resolve endpoint again instead of only the first one.
### 3. Resolve on app startup
Call this once, early in your app's lifecycle — you don't need to wait for a login:
```typescript theme={null}
const deferred = await masivo.deepLinks.resolveDeferred({
customerId, // optional — omit it on a fresh install with no session yet
platform: Platform.OS === "ios" ? "ios" : "android"
});
if (deferred) {
// e.g. Linking.openURL(deferred.url)
}
```
| Parameter | Type | Required | Description |
| - | - | - | - |
| `customerId` | `string` | No | The Masivo customer identifier, if you already have one. Resolution works without it — matching never depends on the customer — and it's only used to attribute the resulting analytics event |
| `platform` | `"ios" \| "android"` | Yes | Current device platform |
| `osVersion` | `string` | No | Improves fingerprint match accuracy when multiple pending clicks share an IP |
| `language` | `string` | No | Same purpose as `osVersion` |
If you pass a `customerId`, it must already exist in Masivo — the call
fails otherwise. If your app creates an anonymous customer profile before
login (a common pattern — see [Register a
customer](/api-reference/guides/register-a-customer)), you can pass that id
here and later merge it into the real customer once the user signs in.
Omitting `customerId` entirely also works and is the simplest option for a
fresh install with no session yet.
The call resolves to `null` when there is nothing to recover. Otherwise it resolves to:
| Field | Type | Description |
| - | - | - |
| `url` | `string` | The destination to open |
| `tracking_id` | `string` | Correlates with the original link click for analytics |
| `match_type` | `"referrer" \| "clipboard" \| "fingerprint"` | How the match was made |
Wrap the call in `try/catch` — a network or server error throws instead of resolving, and the attempt is not marked as done, so it retries automatically on the next app launch.
### How Android and iOS differ
* **Android** works automatically for any link that redirects to a Play Store listing — no configuration needed on the link itself.
* **iOS** requires the link to have **Deferred deep linking via clipboard** enabled in [Advanced Settings](/guides/create-links#recover-the-destination-after-an-app-install). Without it, there's nothing on the clipboard for the app to read, and only IP-based fingerprint matching applies.
### Related documentation
* [Links](/guides/create-links)
* [Integrate push notifications](/api-reference/guides/integrate-push-notification)
# Integrate in-app messages
Source: https://docs.masivo.ai/api-reference/guides/integrate-in-app-messages
Learn how to fetch, display, and track in-app messages using Masivo's REST API
See the full parameter and response reference for [`GET in-app
messages`](/api-reference/customers/\[id]/inapp-messages/get), [`POST log
event`](/api-reference/customers/\[id]/inapp-messages/\[messageId]/events/post),
and [`GET capabilities`](/api-reference/inapp/capabilities/get).
### Prerequisites
Authenticate with the Storefront API using a Bearer access token. See [Integrate with Masivo](/api-reference/guides/integrate-with-masivo).
These endpoints work with **CLIENT** and **SERVER** API keys. Use a SERVER key
when fetching messages from your backend; CLIENT keys are suitable for
embedded apps that call Masivo directly.
### 1. Check capabilities
```typescript theme={null}
const response = await fetch(
"https://app.masivo.ai/api/storefront/v1/inapp/capabilities",
{
headers: {
Authorization: `Bearer ${accessToken}`,
"x-account-id": accountId
}
}
);
const { data } = await response.json();
// { inappEnabled: true, eventTypes: ["PURCHASE", "APP_OPEN"] }
```
### 2. Fetch eligible messages
```typescript theme={null}
const customerId = "user_abc123";
const params = new URLSearchParams({ limit: "5", trigger: "APP_OPEN" });
const response = await fetch(
`https://app.masivo.ai/api/storefront/v1/customers/${customerId}/inapp-messages?${params}`,
{
headers: {
Authorization: `Bearer ${accessToken}`,
"x-account-id": accountId
}
}
);
const { data } = await response.json();
const messages = data.messages;
```
Only `pending` messages that are scheduled, not expired, and under their impression cap are returned.
### 3. Log impression events
```typescript theme={null}
const messageId = messages[0].id;
const payload = { action: "shown" as const };
await fetch(
`https://app.masivo.ai/api/storefront/v1/customers/${customerId}/inapp-messages/${messageId}/events`,
{
method: "POST",
headers: {
Authorization: `Bearer ${accessToken}`,
"x-account-id": accountId,
"Content-Type": "application/json"
},
body: JSON.stringify(payload)
}
);
```
For button clicks:
```typescript theme={null}
const clickPayload = {
action: "clicked",
metadata: { button_id: "primary" }
};
```
### Message content fields
| Field | Description |
| - | - |
| `type` | Surface type: `modal`, `fullscreen`, `slideup`, `banner` |
| `content.title` | Headline |
| `content.body` | Main copy |
| `content.image_url` | Optional hero image |
| `content.buttons` | Action buttons with `action`: `dismiss`, `deeplink`, `url`, `custom` |
| `display_rules.max_impressions` | Server-enforced show limit |
| `display_rules.min_interval_seconds` | Minimum gap between `shown` events |
| `trigger.event` | When set, pass the same value as the `trigger` query param |
### Common errors
| Status | Cause | Example `details` |
| - | - | - |
| `404` | Message not found for customer | `In-app message not found` |
| `422` | Invalid action or query params | Validation message from Zod |
### Related documentation
* [In-app messages concept](/concepts/in-app-messages)
* [In-app messages](/guides/create-in-app-templates)
* [Integrate in-app messages](/guides/in-app-messages)
# Integrate push notifications
Source: https://docs.masivo.ai/api-reference/guides/integrate-push-notification
Learn how to register device tokens for push notifications using Masivo's REST API
Check the complete list of parameters and responses of the [`PATCH Device
Token`](/api-reference/customers/\[id]/devices/patch) endpoint for registering push
notification tokens.
### What are push notifications in Masivo?
Push notifications allow you to send timely, relevant messages directly to your customers' mobile devices. By registering device tokens, you can target specific users or segments with personalized notifications, promotions, or alerts.
### Register a device token
To enable push notifications for a customer, you need to register their device token with Masivo. This links their device to their customer profile, allowing for targeted messaging.
```typescript theme={null}
const customerId = "unique-customer-id";
const payload = {
device_token: "duPXivAiS_uR1o2WGFb5fb:APA91bHYGH0sQcTWtJwSPTRB-CR47-lfo7mh_ygPVHDxBnyW45CjrV_PwVFARL8HLQ1AQOhaJfUxoPfBLtRmCZrTUYPPq9o67aXVQ-khIpiKcf4YvaFnba0",
device_info: {
platform: "android", // "android" | "ios" | "web"
model: "SM-S918B", // Device model identifier
osVersion: "34" // Operating system version
}
};
const response = await fetch(
`https://app.masivo.ai/api/storefront/v1/customers/${customerId}/devices`,
{
method: "PATCH",
headers: {
Authorization: `Bearer ${accessToken}`,
"Content-Type": "application/json"
},
body: JSON.stringify(payload)
}
);
```
### Understanding device tokens
* **device\_token**: A unique identifier provided by Firebase Cloud Messaging (FCM) that allows Masivo to send notifications to a specific device. This token is generated by the Firebase SDK in your mobile application.
* **device\_info**: Additional information about the device that helps with segmentation:
* **platform**: The operating system of the device ("android", "ios", or "web")
* **model**: The device model name or identifier
* **osVersion**: The version of the operating system running on the device
### Rich notification images
The optional **image** field on a push template is a **rich notification attachment** — a banner or hero image displayed alongside the title and body in the notification tray. It is **not** the app icon or launcher logo; those come from your native app bundle.
Masivo uploads the image and includes it in the FCM/APNs payload. Supported formats are JPEG, PNG, and GIF. Keep file sizes reasonable for fast delivery on mobile networks.
### Custom notification sounds
Notification sounds are native app resources, not remote media. Masivo does not
accept an uploaded audio URL because neither FCM nor the operating systems can
use a remote file as a notification sound. Add the same `.wav` file to every
mobile app build that receives the template, then enter its filename in the
Masivo push template.
Use one of these template values:
* Empty: silent notification.
* `default`: device default sound.
* `order_ready.wav`: custom sound bundled in the app.
Use lowercase filenames containing only letters, numbers, and underscores.
Keep custom sounds below 30 seconds for iOS compatibility.
#### Android
Copy `order_ready.wav` to `android/app/src/main/res/raw/`. Android identifies the
resource as `order_ready`, without its extension.
On Android 8 and later, create these channels before receiving notifications:
```typescript theme={null}
import notifee, { AndroidImportance } from "@notifee/react-native";
await Promise.all([
notifee.createChannel({
id: "masivo_silent",
name: "Silent notifications",
importance: AndroidImportance.HIGH
}),
notifee.createChannel({
id: "masivo_default",
name: "Default notifications",
importance: AndroidImportance.HIGH,
sound: "default"
}),
notifee.createChannel({
id: "masivo_order_ready",
name: "Order ready notifications",
importance: AndroidImportance.HIGH,
sound: "order_ready"
})
]);
```
Masivo derives the custom channel ID from the filename:
`order_ready.wav` becomes `masivo_order_ready`. Android channel sound behavior
cannot be changed after a channel is created, and users can override it in
system settings.
#### iOS
Add `order_ready.wav` to the main application target's **Copy Bundle Resources**
build phase. Masivo sends the complete filename in the APNs `sound` field.
Rebuild and reinstall the native app after adding a sound. An over-the-air
JavaScript update cannot add native audio resources.
Device tokens can change over time. Your app should update the token in Masivo
whenever the FCM SDK provides a new token to ensure uninterrupted delivery of
notifications.
### User segmentation with push notifications
Registering device tokens enables powerful audience segmentation capabilities:
1. **Target by demographics**: Send notifications to specific user segments based on demographics, location, or behavior
2. **Campaign-specific messaging**: Tailor notifications to specific marketing campaigns or loyalty programs
3. **Engagement tracking**: Monitor which notifications drive user engagement and conversions
### FCM data payload
When Masivo delivers a visible push, Firebase receives a `notification`
block (`title`, `body`) plus a `data` map. Every `data` value is a **string**.
Image and sound are platform fields (Android/APNs), not keys inside `data`.
Use `data` when the customer opens the notification (deep link, attribution,
or `PUSH_OPENED`). A dashboard **test** send is not the same as a **journey**
send — do not assume the keys are interchangeable.
This section covers **visible** marketing pushes. Silent in-app wake
payloads use `masivo_type` and are documented in the
[React Native SDK](https://www.npmjs.com/package/@masivo/rn).
#### Data keys
| Key | When it is present | Meaning |
| - | - | - |
| `accountId` | Always | Masivo account that sent the push |
| `notificationId` | Test, scheduled audience, manual API | Test is `"test"`. Manual API is `"manual"`. Scheduled audience sends the delivery history id |
| `customerId` | Journey and manual API | Customer who received the push |
| `link` | Template has a destination | Short URL on the account [custom domain](/guides/settings/links) (`https://{domain}/l/{path}`) |
| `redirect_android` | [Push settings](/guides/settings/push) | Store / app fallback for Android |
| `redirect_ios` | Push settings | Store / app fallback for iOS |
| `redirect_web` | Push settings | Web fallback |
`redirect_*` keys are omitted when the corresponding setting is empty.
`link` is a Masivo short URL when one was generated for that send. Two
fallbacks send the **template destination URL** instead:
* **Journey:** the node has no short URL mapped yet
* **Test:** the send is not tied to a stored push template
The host is never `app.masivo.ai`. It is the first custom domain in
[Links settings](/guides/settings/links) (for example
`https://links.example.com/l/AbCdEf`).
#### Test send
Dashboard **Send test** from a [push template](/guides/create-push):
```json theme={null}
{
"notificationId": "test",
"accountId": "00000000-0000-4000-8000-000000000001",
"link": "https://links.example.com/l/AbCdEf",
"redirect_android": "https://play.google.com/store/apps/details?id=com.example",
"redirect_ios": "https://apps.apple.com/app/id123456789",
"redirect_web": "https://example.com"
}
```
No `customerId`. If the test is not tied to a stored template, `link` is the
template destination URL instead of a short URL.
#### Journey send
A **Send push notification** action on a [journey](/guides/create-journeys):
```json theme={null}
{
"customerId": "user_abc123",
"accountId": "00000000-0000-4000-8000-000000000001",
"link": "https://links.example.com/l/XyZ123",
"redirect_android": "https://play.google.com/store/apps/details?id=com.example",
"redirect_ios": "https://apps.apple.com/app/id123456789",
"redirect_web": "https://example.com"
}
```
No `notificationId`. If the journey node already has short URLs, `link` is
that short URL. Otherwise `link` is the template destination URL.
#### Scheduled audience send
**Schedule** from a push template to an audience:
```json theme={null}
{
"notificationId": "11111111-1111-4111-8111-111111111111",
"accountId": "00000000-0000-4000-8000-000000000001",
"link": "https://links.example.com/l/AbCdEf"
}
```
`notificationId` is the delivery history id. No `customerId` on topic or
per-device audience payloads.
#### Manual API send
[`POST /push/customers/{id}/send`](/api-reference/push/customers/\[id]/send/post)
returns `{ data: { sent: true, customer_id, push_template_id } }`. The device
still receives this `data` map:
```json theme={null}
{
"notificationId": "manual",
"accountId": "00000000-0000-4000-8000-000000000001",
"customerId": "user_abc123",
"link": "https://links.example.com/l/AbCdEf"
}
```
#### Compare origins
| Origin | `notificationId` | `customerId` |
| - | - | - |
| Dashboard test | `"test"` | omitted |
| Journey action | omitted | customer id |
| Scheduled audience | delivery history id | omitted |
| Manual API send | `"manual"` | customer id |
#### Handle notification opens
```typescript theme={null}
const data = remoteMessage.data ?? {};
const link = typeof data.link === "string" ? data.link : null;
if (link) {
await Linking.openURL(link);
}
await masivo.push.trackNotificationOpened({
type: "PUSH_OPENED",
customer_id: data.customerId ?? currentCustomerId,
brand_id: brandId,
notification_data: data
});
```
On a test send, `data.customerId` is missing — use the signed-in customer id.
### Best practices
* Always request push notification permissions at appropriate times in the user journey
* Refresh and update device tokens regularly to maintain delivery reliability
* Provide clear opt-out options to respect user preferences
* Test notifications across different device types before sending to your entire audience
* Keep messages concise and actionable to improve engagement
# Integrate with Masivo
Source: https://docs.masivo.ai/api-reference/guides/integrate-with-masivo
Learn how to integrate Masivo with your e-commerce platforms
### 1. Obtain Your API Key
Navigate to **Settings -> Advanced Settings** and copy your API key. This key is essential for authenticating your API requests.
Important! Your API key is unique and must be kept secure in your server.
Start by creating and setting up an account on Masivo to obtain your API key.
***
### 2. Authorize Your API Key
To obtain an access token, use your API key. Send a GET request to the [`/auth/authorize`](/api-reference/auth/authorize/get) endpoint with the API key included in the `x-api-key` header.
Remember that each token lasts for one hour, call the auth endpoint again to
update the token.
```typescript theme={null}
const response = await fetch("https://api.masivo.ai/auth/authorize", {
method: "GET",
headers: { "x-api-key": /* YOUR-API-KEY-HERE */ }
});
```
***
### 3. Use the Access Token
Once you have an access token, use it in all requests that require authentication. The access token will grant you permissions to access and modify the scope of your account.
```typescript theme={null}
const response = await fetch(
"https://app.masivo.ai/api/storefront/v1/rewards",
{
method: "GET",
headers: { Authorization: `Bearer ${accessToken}` }
}
);
```
### 4. Checking the access token expiration
```typescript theme={null}
const response = await decodeJwt(accessToken);
const { exp } = response.payload;
const expired = Date.now() >= exp;
if (expired) {
// refresh access token
} else {
// continue
}
```
### Full example
```typescript theme={null}
import { decodeJwt } from "jose";
const baseUrl = "https://api.masivo.ai/api/storefront/v1";
let accessToken;
// Get an access token that is safe to use
const getBearerToken = async () => {
const decoded = accessToken ? await decodeJwt(accessToken) : undefined;
const { exp = 0 } = decoded?.payload ?? {};
// 5 minutes before it actually expires
const expirationTime = exp * 1000 - 5 * 60 * 1000;
const expired = Date.now() >= expirationTime;
const refreshToken = !accessToken || expired;
if (!refreshToken) return `Bearer ${accessToken}`;
const url = `${baseUrl}/auth/authorize`;
const headers = new Headers();
headers.set("x-api-key", process.env.API_KEY);
const res = await fetch(url, { headers });
access_token = (await res.json()).data;
return `Bearer ${accessToken}`;
};
// Use this token as an authorization header in each subsequent api call
const token = await getBearerToken();
// Your code...
```
# Journey Triggers
Source: https://docs.masivo.ai/api-reference/guides/journey-triggers
Complete guide for triggering automated customer journeys in Masivo using events - from cart abandonment to personalized follow-ups
## Overview
Journey triggers in Masivo enable developers to create sophisticated automated workflows that respond to customer behavior in real-time. By sending events through the API, you can trigger journey transitions that move customers through personalized experiences based on their actions and attributes.
Journey triggers work by matching events to journey edge configurations. When
an event matches the specified event type and conditions, customers
automatically progress to the next step in the journey.
## Understanding Journey Triggers
### How Journey Triggers Work
Journey triggers operate through a three-step process:
1. **Event Emission**: Developer sends an event via the [`POST /behavior/events`](/api-reference/behavior/events/post) endpoint
2. **Journey Evaluation**: Masivo evaluates all active journeys to find matching trigger conditions
3. **Customer Progression**: Qualifying customers automatically move to the next journey step
### Journey Edge Configuration
Each journey transition (edge) can be configured with:
* **Event Type**: The specific event that triggers the transition (e.g., `ADD_TO_CART`, `PURCHASE`)
* **Conditions**: Additional criteria that must be met for the transition to occur
* **Wait Time**: Delay in minutes before the next action is executed
## 1. Sending Events to Trigger Journeys
### Basic Event Structure
Use the [`POST /behavior/events`](/api-reference/behavior/events/post) endpoint to send events that can trigger journey transitions:
```typescript theme={null}
const triggerJourneyEvent = async (eventData: {
type: string;
customer_id: string;
brand_id: string;
[key: string]: any;
}) => {
const response = await fetch(
"https://app.masivo.ai/api/storefront/v1/behavior/events",
{
method: "POST",
headers: {
Authorization: `Bearer ${accessToken}`,
"Content-Type": "application/json"
},
body: JSON.stringify(eventData)
}
);
if (response.ok) {
const result = await response.json();
console.log("Event sent successfully:", result.data);
return result.data;
} else {
const error = await response.json();
console.error("Event failed:", error.details);
throw new Error(error.details);
}
};
```
### Event Processing for Journeys
When you send an event, Masivo automatically:
1. **Enqueues for Journey Processing**: Events are added to the `journeys_events` queue
2. **Evaluates Active Journeys**: Checks all active journeys for matching trigger conditions
3. **Processes Customer Transitions**: Moves qualifying customers to the next journey step
4. **Applies Wait Times**: Schedules future actions based on configured delays
## 2. Cart Abandonment Journey Example
### Scenario: E-commerce Cart Abandonment Recovery
Let's build a complete cart abandonment journey that triggers when customers add items to cart but don't complete purchase within 30 minutes.
#### Step 1: Journey Configuration
For this example, create and activate the journey first. The email action node
should use a previously configured email template. See
[Create Journeys](/guides/create-journeys) and
[Creating an email template](/guides/emails/create-email).
In the journey editor, use the `+` on the path to add steps. You can choose
**On event**, **Condition**, or **Wait for event**; the editor then shows an
**Actions** node where you add what to send (for this example, an email). For a
full walkthrough, see [Create Journeys](/guides/create-journeys).
The journey would be configured with these transitions:
**Trigger Edge (Root → Wait Node)**:
* **Event Type**: `ADD_TO_CART`
* **Conditions**: No conditions required (triggers for any cart addition)
* **Wait Time**: 30 minutes
**Follow-up Edge (Wait Node → Email Node)**:
* **Event Type**: `null` (time-based transition)
* **Conditions**: Customer has not made a purchase
* **Wait Time**: 0 minutes (fires immediately after wait period)
#### Step 2: Send ADD\_TO\_CART Event
```typescript theme={null}
const sendAddToCartEvent = async (customerId: string, productData: any) => {
const eventData = {
type: "ADD_TO_CART",
customer_id: customerId,
brand_id: "your-brand-id",
product: {
sku: productData.sku,
amount: productData.quantity,
value: productData.price,
tags: {
category: productData.category,
brand: productData.brand
},
metadata: {
product_name: productData.name,
image_url: productData.image
}
},
tags: {
cart_value: productData.price * productData.quantity,
cart_items_count: 1,
last_cart_update: new Date().toISOString()
}
};
return await triggerJourneyEvent(eventData);
};
// Example usage
await sendAddToCartEvent("customer-123", {
sku: "PROD-001",
name: "Premium Headphones",
price: 199.99,
quantity: 1,
category: "electronics",
brand: "AudioTech",
image: "https://example.com/headphones.jpg"
});
```
#### Step 3: Handle Purchase Completion
```typescript theme={null}
const sendPurchaseEvent = async (customerId: string, orderData: any) => {
const eventData = {
type: "PURCHASE",
customer_id: customerId,
brand_id: "your-brand-id",
order: {
purchase_id: orderData.orderId,
value: orderData.total,
products: orderData.items.map(item => ({
sku: item.sku,
amount: item.quantity,
value: item.price
})),
payment_method: orderData.paymentMethod
},
tags: {
order_completed: true,
purchase_value: orderData.total,
purchase_date: new Date().toISOString()
}
};
return await triggerJourneyEvent(eventData);
};
```
When a `PURCHASE` event is received, customers are automatically removed from the cart abandonment journey, preventing them from receiving the abandonment email.
## Push payload on journey sends
When a journey step **sends a push**, the device receives a different FCM
`data` payload than a dashboard test send (`customerId` present,
`notificationId` omitted). See [FCM data
payload](/api-reference/guides/integrate-push-notification#fcm-data-payload).
## Conclusion
Journey triggers in Masivo provide a powerful way to create responsive, automated customer experiences. By strategically sending events and configuring journey transitions with appropriate wait times and conditions, you can build sophisticated workflows that engage customers at the perfect moments.
The combination of real-time event processing, flexible timing controls, and condition-based branching enables you to create personalized customer journeys that adapt to individual behavior and preferences, driving engagement and conversions at scale.
For more information about creating and managing journeys, see the [Create Journeys Guide](/guides/create-journeys).
# Migrate customers
Source: https://docs.masivo.ai/api-reference/guides/migrate-customers
Learn how to migrate customers from your old system using Masivo's REST API
Check the complete list of parameters and responses of the [`POST customers
migrate`](/api-reference/customers/migrate/post) endpoint to do a migration.
```typescript theme={null}
const data = {
customers: [
{
id: "your-id", // unique per account
name: "John Smith",
email: "john@smith.com", // unique per account
registration_date: "1995-02-15T00:00:00.000Z",
metrics: { // optional - customer metrics for different time periods (lifetime, yearly, monthly, quarterly)
lifetime: {
to: "2025-02-21T14:05:42.164Z",
from: "2024-11-27T17:57:13.756Z",
tier_id: "8f26a2be-f922-4693-a273-9d7ab317024c",
last_visit: "2024-12-10T20:58:01.965Z",
spend_total: 50.2,
number_of_purchases: 10
},
last_year: {
to: "2025-01-01T07:59:59.999Z",
from: "2024-01-01T08:00:00.000Z",
tier_id: "142b1336-ef6e-40aa-be3f-33332003bfa5",
last_visit: "2024-12-10T20:58:01.965Z",
spend_total: 5.2,
number_of_purchases: 3
},
this_year: {
to: "2026-01-01T07:59:59.999Z",
from: "2025-01-01T08:00:00.000Z",
tier_id: "8f26a2be-f922-4693-a273-9d7ab317024c",
last_visit: "2024-12-10T20:58:01.965Z",
spend_total: 0,
number_of_purchases: 0
},
last_month: {
to: "2025-02-01T07:59:59.999Z",
from: "2025-01-01T08:00:00.000Z",
tier_id: "142b1336-ef6e-40aa-be3f-33332003bfa5",
last_visit: "2024-12-10T20:58:01.965Z",
spend_total: 10,
number_of_purchases: 1
},
this_month: {
to: "2025-03-01T07:59:59.999Z",
from: "2025-02-01T08:00:00.000Z",
tier_id: "8f26a2be-f922-4693-a273-9d7ab317024c",
last_visit: "2024-12-10T20:58:01.965Z",
spend_total: 0,
number_of_purchases: 0
},
updated_at: "2025-02-21T14:05:42.164Z",
last_quarter: {
to: "2025-01-01T07:59:59.999Z",
from: "2024-10-01T07:00:00.000Z",
tier_id: "142b1336-ef6e-40aa-be3f-33332003bfa5",
last_visit: "2024-12-10T20:58:01.965Z",
spend_total: 0,
number_of_purchases: 0
},
this_quarter: {
to: "2025-04-01T06:59:59.999Z",
from: "2025-01-01T08:00:00.000Z",
tier_id: "8f26a2be-f922-4693-a273-9d7ab317024c",
last_visit: "2024-12-10T20:58:01.965Z",
spend_total: 0,
number_of_purchases: 0
},
last_semester: {
to: "2025-01-01T07:59:59.999Z",
from: "2024-07-01T07:00:00.000Z",
tier_id: "142b1336-ef6e-40aa-be3f-33332003bfa5",
last_visit: "2024-12-10T20:58:01.965Z",
spend_total: 10,
number_of_purchases: 1
},
this_semester: {
to: "2025-07-01T06:59:59.999Z",
from: "2025-01-01T08:00:00.000Z",
tier_id: "8f26a2be-f922-4693-a273-9d7ab317024c",
last_visit: "2024-12-10T20:58:01.965Z",
spend_total: 0,
number_of_purchases: 0
},
},
balance: [
{
amount: 2, // the amount of the balance item
brand_id: null, // the id of the brand that owns the reward of this balance item.
expiration_date: null, // the expiration date of the balance item
reward_id: "b2bc86ad-5ccd-4fea-95fd-322a4a8f3f43",
channel_id "e-commerce2", // optional, it has to already exist in your account
funded_by: "marketing", // optional, who funded the rewards used in this balance item
issued_at: "2025-01-05T00:00:00.000Z" // optional, the date when the balance item was originally issued. Defaults to right now.
store_id: "cfc" // optional, it has to already exist in your account
}
],
devices: [
{
device_token: "fcm-token-123456789", // the unique token that identifies the device (given by firebase)
device_info: {
platform: "ios", // the platform of the device (ios, android, web, windows, macos)
model: "iPhone 13", // the model of the device
osVersion: "15.4" // the operating system version of the device
},
expires_at: "2025-12-31T23:59:59.999Z", // the date when the device token expires
created_at: "2024-01-01T10:00:00.000Z" // the date when the device was registered
}
]
}
]
};
const response = await fetch("https://api.masivo.ai/customers/migrate", {
method: "POST",
headers: { Authorization: `Bearer ${accessToken}` },
body: JSON.stringify(data)
});
```
You must not migrate deleted customers
### Batch Processing
Customer migrations are handled in batches. You can include **up to 1000 records per batch**. Keep in mind that:
* **Batch Size:** Ensure your migration payload does not exceed 1000 users.
* **Queuing:** Migrations are not executed instantly; they are **enqueued** for processing. You may need to wait from a few seconds to a few minutes before the changes are visible in your dashboard, depending on how busy the queue is.
### Migration rate limits
Production accounts can send up to **15 requests every 5 seconds** and
**10,000 requests per hour** to each migration endpoint. Each endpoint has an
independent quota, and every request may still contain up to 1,000 customers.
Use the `X-RateLimit-*` response headers to pace requests and honor
`Retry-After` after a `429` response.
If you need to migrate several thousands of records, make sure you are in a
Production account.
# Migrate events
Why migrate events? Migrating events is useful when you want to bring along your customers' behavior events such as purchases.
By default, all migrated events are marked as fulfilled and do not participate in active campaigns.
Unlike transactions, events can be migrated multiple times if you don’t have
all of them available at once.
This section assumes you already had migrated your customers
Check the complete list of parameters and responses of the [`POST events
migrate`](/api-reference/customers/\[id]/events/migrate/post) endpoint to
perform a migration.
```typescript theme={null}
const data = {
customers: [
{
id: "your-customer-id",
events: [
{
brand_id: null, // the brand to which the reward belongs, null if the reward does not have a brand
issued_at: "2025-01-30T17:50:19.423+00:00",
resolved_at: "2025-01-30T17:50:19.423+00:00",
type: "PURCHASE",
fulfilled: true,
// necessary for the metrics calculation
order: {
purchase_id: "1",
value: 10,
products: [
{
sku: "1",
amount: 1,
value: 1
}
],
payment_method: "CASH"
}
}
]
}
]
};
const response = await fetch(
"https://api.masivo.ai/api/storefront/v1/customers/migrate/events",
{
method: "POST",
headers: { Authorization: `Bearer ${accessToken}` },
body: JSON.stringify(data)
}
);
```
You must not migrate reversed events
### Batch Limits
* The total number of events in the batch must not exceed 1000 and each customer must have at least one event.
### Automatic Processing and Metrics
When events are migrated, the system automatically recalculates the customer's metrics and updates their tier based solely
on events with the PURCHASE type.
# Migrate transactions
What is a transaction, and why is it important to migrate them? A transaction represents the history of accumulations and redemptions.
Migrating transactions allows you to have a clear record that justifies the user's previously migrated balance, providing a transparent
explanation of why their wallet holds a specific amount.
For transactions to be processed successfully, they must match the customer's
balance. This is crucial to prevent any discrepancies or errors during the
migration. Ensure that the transactions you are migrating align with the
existing balance information of each customer.
This section assumes you already had migrated your customers and events
Check the complete list of parameters and responses of the [`POST transactions
migrate`](/api-reference/customers/migrate/transactions/post) endpoint to
perform a migration.
```typescript theme={null}
const data = {
customers: [
{
id: "your-customer-id",
transactions: [
{
brand_id: null, // the brand to which the reward belongs
reward_id: "ec9a5bfb-c2f1-4cf0-b509-247e47b96b65",
details: "Migrated from old system", // details about this migrated transaction
type: "ACCUMULATION",
issued_at: "2025-01-30T17:50:19.423+00:00",
resolved_at: "2025-01-30T17:50:19.423Z",
cost: null,
event: null,
channel_id: null,
store_id: null,
amount: 4 // positive if it is ACCUMULATION type
},
{
brand_id: null, // the brand to which the reward belongs
reward_id: "ec9a5bfb-c2f1-4cf0-b509-247e47b96b65",
details: "Migrated from old system", // details about this migrated transaction
type: "REDEMPTION",
issued_at: "2025-01-30T17:50:19.423+00:00",
resolved_at: "2025-01-30T17:50:19.423Z",
cost: null,
event: null,
channel_id: null,
store_id: null,
amount: -2 // negative if it REDEMPTION type
}
]
}
]
};
const response = await fetch(
`https://api.masivo.ai/api/storefront/v1/customers/migrate/transactions`,
{
method: "POST",
headers: { Authorization: `Bearer ${accessToken}` },
body: JSON.stringify(data)
}
);
```
Once transactions are migrated, you cannot migrate them again unless you first
revert the customers' migration. This ensures data integrity and prevents
duplicate transactions.
### Batch Limits
* Ensure your migration payload does not exceed 1000 transactions.
# Revert migration
Mistakes can happen during the migration process at any stage. To address this, we provide a solution through the revert customer migration endpoint.
Transactions and events cannot be reverted independently; instead, you must revert the entire customer migration. This ensures that all related data is automatically rolled back.
**Important:** Only migrated customers that have not produce a transaction can be reverted. This will also include all their related events and transactions.
Check the complete list of parameters and responses of the [`DEL revert
migration`](/api-reference/customers/migrate/revert/delete) endpoint to do a
migration.
```typescript theme={null}
const data = {
start_date: "2024-08-15T00:00:00.000Z", // migrated after
end_date: "2025-05-15T00:00:00.000Z" // migrated before
};
const response = await fetch("https://api.masivo.ai/customers/migrate", {
method: "DEL",
headers: { Authorization: `Bearer ${accessToken}` },
body: JSON.stringify(data)
});
```
# Modify customer balance arbitrarily
Source: https://docs.masivo.ai/api-reference/guides/modify-customer-balance-arbitrarily
Learn how to modify wallets arbitrarily using Masivo's REST API
You should only modify customers' balances when you are certain of what you
are doing and have the proper authorization to do so.
## When to modify wallets
1. **Testing**: You can use this feature to test your application's behavior with
different wallet balances.
2. **Customer support**: Sometimes, you may need to adjust a customer's balance
due to a mistake or a special situation.
3. **Promotions**: You can use this feature to give customers a bonus for a
promotion or a special event.
## Which modifications can be made
* **Add funds**: Create new "accumulation" transactions of a given reward.
* **Subtract funds**: Create new "redemption" transactions of a given reward.
## Limitations
You will not be able to modify the existing transaction history of a customer. This means that new modifications will be added as new transactions.
## How to modify wallets
### Add funds
Create a new custom event type with a name that fits what you want to do. For instance: *corrections*.
Then create a new campaign that triggers when this event is emitted.
This campaign should have a rule that grants the customer a reward of the amount you want to add to their balance.
Finally, emit the event with the customer's ID as the target.
Use the [Emit event](/api-reference/behavior/events/post) endpoint to emit the event.
Include in the metadata a 'reason' field to explain why the balance was
modified.
### Subtract funds
You don't need to create a customer event type for this operation. But we recommend you to emit and event with a custom 'type' as shown in the example below.
Use the [Emit event](/api-reference/behavior/events/post) endpoint to emit a new event including the rewards you would like to subtract from the customer's balance.
You will need to use the 'redeem' field in the top level of the event. This field does not validate if a reward can be redeemed, so you can use it to subtract funds arbitrarily.
Include in the metadata a 'reason' field to explain why the balance was
modified.
# Redeem a coupon
Source: https://docs.masivo.ai/api-reference/guides/redeem-a-coupon
Learn how to redeem a coupon using Masivo's REST API
Check the complete list of parameters and responses of the [`POST Redeem coupon`](/api-reference/coupons/redeem/post) endpoint to get coupon redemption data
### What is a coupon?
A coupon is a code created in a specific coupon campaign that grants one or more rewards according to the campaign's
settings.
```typescript theme={null}
const payload = {
customer_id: "unique-customer-id",
code: "coupon-code",
brand_id: "unique-brand-id", // string | null
// Optional values
store_id: "unique-store-id",
channel_id: "unique-channel-id"
};
const response = await fetch(
"https://app.masivo.ai/api/storefront/v1/coupons/redeem",
{
method: "POST",
headers: { Authorization: `Bearer ${accessToken}` },
body: JSON.stringify(payload)
}
);
```
When a coupon is redeemed, depending on the amount of reward types the coupon gives, each one appear as a new item in
`response.data.accumulations` and indicates the `amount` of that reward type.
The response returns `wallet.totals` updated after coupon redemptions.
It contains the sum of the `amount` of the rewards just redeemed and the `amount` of the specific reward types the
wallet already had.
### What happens when a coupon campaign expires?
When a coupon campaign expires, all associated coupon codes become invalid and cannot be redeemed.
Any attempt to redeem an expired coupon will result in an error.
It is important to highlight that a coupon campaign may still have active coupon codes, but the reward itself may expire
before or after the campaign ends, depending on the configured expiration.
Additionally, the code may return an error even if it is valid, in case the coupon's rewards are out of stock.
### What happens when a code is invalid?
If a coupon code is invalid, the system returns an error, and no rewards are accumulated.
### Coupons validation errors
A Coupon Campaign can be set with specific conditions. If a coupon redemption is attempted and the campaign has a
condition that is not met, the response will include a `requirement` attribute with the default value `"Condition was not
met"`.
This `requirement` message will be a "custom error message" if it is set for the campaign's condition.
# Register a customer
Source: https://docs.masivo.ai/api-reference/guides/register-a-customer
Learn how to register a customer using Masivo's REST API
Check the complete list of parameters and responses of the [`GET
customers`](/api-reference/customers/\[id]/get) endpoint to get customer data,
and the the [`POST customer`](/api-reference/customers/post) endpoint to
register a new customer.
```typescript theme={null}
const newCustomer = {
id: "unique-customer-id",
name: "John Doe",
email: "john.doe@example.com",
// Optional values
gender: "MALE",
dob: "2003-11-07T05:31:56Z",
metadata: {
/* your additional info */
}
};
const response = await fetch("https://api.masivo.ai/customers", {
method: "POST",
headers: { Authorization: `Bearer ${accessToken}` },
body: JSON.stringify(newCustomer)
});
```
### Check for Existing Customers
Before registering a new customer, it’s often helpful to check whether they already exist in the database. To do this, you can make a GET request to the "/customers" endpoint.
You might be asking, *"How can I check the 'customers' endpoint if I don’t have the `customerId` yet?"* That’s where your service’s customer ID comes into play. When registering a new customer,
you can include your own unique identifier for them. By using that ID, you can call the endpoint to check if the customer is already registered. If they are,
their details will be returned; if not, you’ll receive a 404 response, signaling that it’s safe to proceed with the registration.
### Flow diagram example
Do not use this guide to migrate customers. Please refer to the [`Migrate
customers`](/api-reference/guides/migrate-customers) guide instead.
## Related architecture
* [Registration flow](/resources/data-architecture#registration-flow) — What Masivo creates after signup: profile, wallet, loyalty event, and sync
# Reverse a coupon
Source: https://docs.masivo.ai/api-reference/guides/reverse-a-coupon
Learn how to reverse a coupon redemption using Masivo's REST API
Check the complete list of parameters and responses of the [`POST Reverse
coupon`](/api-reference/coupons/reverse/post) endpoint to reverse a coupon
redemption
### What is coupon reversal?
Coupon reversal allows you to undo a coupon redemption and return the rewards back to the system. This is useful when:
* A customer needs to cancel an order that used a coupon
* A coupon was redeemed by mistake
* You need to correct an erroneous redemption
When a coupon is reversed, the system:
1. Removes the rewards from the customer's wallet
2. Creates reversal transactions for audit purposes
3. Updates the coupon redemption status to `REVERSED`
4. Creates a `REVERSED_COUPON` event
5. Recalculates the customer's tier if necessary
```typescript theme={null}
const payload = {
customer_id: "unique-customer-id",
code: "coupon-code"
};
const response = await fetch(
"https://app.masivo.ai/api/storefront/v1/coupons/reverse",
{
method: "POST",
headers: { Authorization: `Bearer ${accessToken}` },
body: JSON.stringify(payload)
}
);
```
### Response
The response returns the updated wallet after reversing the coupon, with the rewards removed from `wallet.totals`.
The `redemptions` array contains the lines that were reversed (negative amounts).
```json theme={null}
{
"data": {
"customer_id": "unique-customer-id",
"wallet": {
"totals": {
"reward-id-1": 450,
"reward-id-2": 0
}
},
"accumulations": [],
"redemptions": [
{
"reward_id": "reward-id-1",
"amount": -50,
"issued_at": "2024-01-15T10:30:00Z"
}
]
}
}
```
### What happens when you try to reverse a coupon that was already reversed?
If you attempt to reverse a coupon that has already been reversed, the system will return an error:
```json theme={null}
{
"error": "Internal validation failed",
"details": "This coupon redemption has already been reversed"
}
```
### What happens when the rewards have already been spent?
If the customer has already used (spent or redeemed) the rewards obtained from the coupon, the reversal will fail with an error:
```json theme={null}
{
"error": "Internal validation failed",
"details": "Rewards have already been spent or redeemed and cannot be reversed"
}
```
This validation ensures that you cannot reverse a coupon if the customer has already used those rewards in a transaction.
### What happens when the rewards are currently reserved?
If the rewards from the coupon are currently reserved for a pending order or transaction, the reversal will fail with an error:
```json theme={null}
{
"error": "Internal validation failed",
"details": "Rewards are currently reserved and cannot be reversed"
}
```
Rewards are considered "reserved" when they are being held for a transaction that has not yet been completed or fulfilled. This happens when:
* A customer has an unfulfilled order that is using the rewards
* The rewards are in a reservation state waiting for order confirmation
* A pending transaction is holding the rewards
To reverse a coupon with reserved rewards, you must first:
1. Cancel or complete the pending transaction using the reserved rewards
2. Wait for the reservation to expire (if applicable)
3. Then attempt the coupon reversal again
### What happens when the coupon redemption is not found?
If the coupon redemption does not exist for the specified customer and code, the system will return:
```json theme={null}
{
"error": "Resource not found",
"details": "Coupon redemption not found"
}
```
### Important considerations
* ⚠️ **Only active redemptions can be reversed**: You cannot reverse a coupon that has already been reversed
* ⚠️ **Rewards must not be spent**: The customer must have the full amount of rewards in their wallet
* ⚠️ **Rewards must not be reserved**: If rewards are being held for a pending transaction, they cannot be reversed
* ⚠️ **Time-sensitive operation**: It's recommended to reverse coupons as soon as possible after redemption
* ⚠️ **Audit trail**: All reversals create `REVERSAL` transactions and a `REVERSED_COUPON` event for tracking
* ⚠️ **Check order status first**: Before reversing a coupon, ensure no pending orders are using the rewards
### Use cases
**1. Order Cancellation**
```typescript theme={null}
// Customer cancels an order that used a coupon
const reverseResponse = await reverseCoupon({
customer_id: "customer-123",
code: "SUMMER2024"
});
```
**2. Mistaken Redemption**
```typescript theme={null}
// Customer accidentally redeemed wrong coupon
const reverseResponse = await reverseCoupon({
customer_id: "customer-456",
code: "WRONGCODE"
});
```
**3. Fraud Prevention**
```typescript theme={null}
// Detected fraudulent redemption
const reverseResponse = await reverseCoupon({
customer_id: "suspicious-customer",
code: "FRAUD123"
});
```
### Reversal workflow
```mermaid theme={null}
graph TD
A[Customer requests reversal] --> B{Redemption exists?}
B -->|No| C[Return 404 Not Found]
B -->|Yes| D{Already reversed?}
D -->|Yes| E[Return 422 Already Reversed]
D -->|No| F{Rewards reserved?}
F -->|Yes| G[Return 422 Currently Reserved]
F -->|No| H{Rewards spent?}
H -->|Yes| I[Return 422 Cannot Reverse]
H -->|No| J[Remove rewards from wallet]
J --> K[Create REVERSAL transactions]
K --> L[Update redemption status]
L --> M[Create REVERSED_COUPON event]
M --> N[Return success response]
```
### Events created
When a coupon is reversed, a `REVERSED_COUPON` event is automatically created with the following data:
```json theme={null}
{
"type": "REVERSED_COUPON",
"data": {
"customer_id": "unique-customer-id",
"brand_id": "unique-brand-id",
"code": "coupon-code",
"campaign_id": "campaign-uuid"
}
}
```
This event can be used for:
* Analytics and reporting
* Integration with external systems
* Triggering marketing automation workflows
* Audit and compliance purposes
# Send an event
Source: https://docs.masivo.ai/api-reference/guides/send-an-event
Learn how to send an event to Masivo's REST API
Check the complete list of parameters and responses of the [`POST Emit
event`](/api-reference/behavior/events/post)
### What is an event?
A Customer Event is any action performed by a customer that can activate certain behavior-based campaigns.
For instance, events can include actions like completing a purchase, submitting a survey, or registering on your platform.
You can send different types of events to Masivo, which will monitor them and apply your configured rules to distribute rewards through campaigns.
However, only events that match the campaign's specific criteria will successfully trigger those campaigns.
### Emit an Event
When emitting an event, the customer, brand, and event type must be defined and included in the request. The other values are optional, depending on the nature of the event to be emitted.
```typescript theme={null}
const payload = {
customer_id: "unique-customer-id",
brand_id: "unique-brand-id",
type: "PURCHASE",
// Optional: ISO 8601 timestamp with a timezone (UTC Z or an explicit offset)
issued_at: "2026-03-25T10:00:00.000Z",
// Optional values
order: {
purchase_id: "unique-purchase-id",
channel_id: "unique-channel-id",
store_id: "unique-store-id",
value: 20.95,
discounted_value: 10.95,
products: [
{
sku: "",
amount: 123,
value: 123,
discounted_value: 123,
redeem: [
{
id: "3c90c3cc-0d44-4b50-8888-8dd25736052a",
amount: 1,
},
],
tags: {},
metadata: {},
},
],
shipping: {
value: 3.25,
discounted_value: 2.25,
redeem: [
{
id: "3c90c3cc-0d44-4b50-8888-8dd25736052a",
amount: 1,
},
],
tags: {},
metadata: {},
},
payment_method: "CREDIT", "CREDIT" | "DEBIT"
redeem: [
{
id: "3c90c3cc-0d44-4b50-8888-8dd25736052a",
amount: 1,
},
],
metadata: {},
},
fulfilled: true,
reserve: 1,
redeem: [
{
id: "3c90c3cc-0d44-4b50-8888-8dd25736052a",
amount: 1,
},
],
tags: {},
metadata: {},
}
const response = await fetch("https://app.masivo.ai/api/storefront/v1/behavior/events", {
method: "POST",
headers: { Authorization: `Bearer ${accessToken}` },
body: JSON.stringify(payload)
});
```
The `issued_at` field is optional. If omitted, the server will use the current time. When provided, it must be a valid ISO 8601 date that is **not in the future** and **not older than 72 hours**.
Always include timezone information. Send UTC with `Z` (recommended), such as `2026-03-25T10:00:00.000Z`, or include an explicit offset, such as `2026-03-25T05:00:00.000-05:00`. These two examples represent the same moment. Do not append `Z` to a local time without converting it to UTC first.
### Fulfill an event
If the `fulfilled` attribute has been sent as `false` in the body of the [POST Emit event](/api-reference/behavior/events/post) endpoint,
the event will be placed in a standby state. The reward won't be added to the wallet until `fulfilled` is `true`.
It is important to note that when the event was not emitted with `fulfill` set to `true`, the rewards will not be redeemed until it is explicitly fulfilled.
It is also worth mentioning that the event will expire after 24 hours (by default depending on `reserve` value) if it has not been set to fulfill.
In this case, you will need to manually execute the fulfill if you want the event to be processed.
For this, it is necessary to send the event id of the event to the [PATCH Fulfill event](/api-reference/behavior/events/\[eventId]/fulfill/patch) endpoint.
```typescript theme={null}
const response = await fetch(
`https://app.masivo.ai/api/storefront/v1/behavior/events/${eventId}/fulfill`,
{
method: "PATCH",
headers: { Authorization: `Bearer ${accessToken}` }
}
);
```
After the event has been successfully fulfilled, The reward will be added to the wallet.
### Reverse an event
If you want to reverse an event and all actions resulting from it, you must send the event id to the [`POST
Reverse event`](/api-reference/behavior/events/\[eventId]/reverse/delete) endpoint, including a reason in the body of the request.
```typescript theme={null}
const payload = {
reason: "" // The reason why you are reversing the event
};
const response = await fetch(
`https://app.masivo.ai/api/storefront/v1/behavior/events/${eventId}/reverse`,
{
method: "POST",
headers: {
Authorization: `Bearer ${accessToken}`,
"Content-Type": "application/json"
},
body: JSON.stringify(payload)
}
);
```
Once the event has been reversed, all accumulation, redemption, and updating of customer metrics will be reversed.
It is important to note that once an event has been reversed, its `STATUS` will change to `"REVERSED"`.
### What happens when an event expires?
An event expires when it's hasn't been sent with `fulfill` in `true` and it's been 24 hours since that.
The amount of days for expiration time can be extended by sending `reserve` in the body of the request.
### What happens if the event is reversed when is still unfulfilled?
If an event is emitted with `fulfill` in `false` and then it is reversed, the event will be cancelled. The `status` in the response of the request will be set in `"CANCELLED"`.
### How reserves can be extended?
The reserve days can be extended by setting the `reserve` attribute in the `body` of the [`POST Emit event`](/api-reference/behavior/events/post) endpoint. So the value sent in `reserve`
will be equal to the number of days the reserves will be extended.
## Related architecture
* [Activity & events](/resources/data-architecture#activity-events) — Loyalty vs tracking-only processing and what each path updates
* [Marketing & destinations](/resources/data-architecture#marketing-destinations) — How emitted events can trigger journeys and destination sync
# Setup webhooks
Source: https://docs.masivo.ai/api-reference/guides/setup-webhooks
Learn how to set up webhooks in Masivo to receive notifications about events, rewards, and more
Event-derived notifications include `data.attribution` when the source event
has campaign context; reversals use the original event. The field is omitted
when context is absent. See the [shared contract](/api-reference/guides/attribution-tracking).
## What are webhooks?
Webhooks enable real-time communication between servers. They allow one application to automatically send data to another whenever a specific event occurs.
## Webhooks in Masivo
Masivo allows you to set up webhooks to receive notifications about several events including:
* Rewards accumulations & redemptions
* Customer registrations
* Event fulfillments & failures
* Coupon redemptions
* Tier upgrades & downgrades
Check our [full list](/api-reference/guides/setup-webhooks/#masivo-webhook-events) of events and their payloads
## Setting up webhooks in Masivo
To set up webhooks in Masivo, first navigate to the **Settings** section in the dashboard. You can find it in the left sidebar. Then click on the **Webhooks** menu.
Now, click on the **Add new webhook** button. You will be prompted to enter the URL where you want to receive the notifications.
The URL must follow the HTTPS protocol. Masivo will send a **POST request** to
this URL with the event payload.
Finally, select the events you want to receive notifications for. You can choose from the list of available events. Then click on the **Save** button.
You can setup multiple webhooks to receive notifications for different events. Is up to you to decide which events you want to receive in each webhook.
## Masivo webhook events
### Registration
Triggers when a new customer is registered to your program.
Migrated customers will not trigger this event
```json theme={null}
{
"event": "CUSTOMER_REGISTRATION",
"data": {
"customer_id": "",
// Customer data
"customer": {...},
// Customer's wallet
"wallet": {...}
}
}
```
### Reward accumulation
Triggers when a customer accumulates any type of reward. This includes when a behavior event is fulfilled or when a coupon is redeemed.
```json theme={null}
{
"event": "CUSTOMER_ACCUMULATION",
"data": {
"customer_id": "",
// List of accumulated reward lines
"lines": [...],
// The updated wallet
"wallet": {...}
}
}
```
### Reward redemption
Triggers when a customer redeems any type of reward. Most commonly, this event is triggered when a customer redeem rewards in a PURCHASE behavior event.
```json theme={null}
{
"event": "CUSTOMER_REDEMPTION",
"data": {
"customer_id": "",
// List of redeemed reward lines
"lines": [...],
// The updated wallet
"wallet": {...}
}
}
```
### Reward reservation
Triggers when a customer reserves a reward. This event is triggered when a PURCHASE behavior event is set with `fulfilled: false`.
```json theme={null}
{
"event": "REWARDS_RESERVATION",
"data": {
"customer_id": "",
// List of reserved reward lines
"lines": [...],
// The updated wallet
"wallet": {...}
}
}
```
### Reward reservation cancelled
Triggers when a behavior event is cancelled before it was fulfilled. The event will notify the new state of the customer wallet along the released lines.
```json theme={null}
{
"event": "REWARDS_RESERVATION_CANCELLED",
"data": {
"customer_id": "",
// List of released reward lines
"lines": [...],
// The updated wallet
"wallet": {...}
}
}
```
### Behavior event fulfilled
Triggers when a behavior event is fulfilled.
```json theme={null}
{
"event": "BEHAVIOR_EVENT_FULFILLED",
"data": {
"customer_id": "",
// The fulfilled behavior event
"event": {...}
}
}
```
### Behavior event failed
Triggers when a behavior event fails to complete.
This webhook event may be sent multiple times as Masivo retries failed events.
```json theme={null}
{
"event": "BEHAVIOR_EVENT_FAILED",
"data": {
"customer_id": "",
// The failed behavior event
"event": {...}
}
}
```
### Coupon redemption
Triggers when a customer redeems a coupon.
```json theme={null}
{
"event": "COUPON_REDEMPTION",
"data": {
"customer_id": "",
// The id of the campaign where the coupon was redeemed
"campaign_id": "",
// The redeemed coupon code
"code": ""
}
}
```
### Tier upgrade
Triggers when a customer's tier is upgraded. This can happen when a customer accumulates enough points or meets the entry requirements for a higher tier.
```json theme={null}
{
"event": "TIER_UPGRADE",
"data": {
"customer_id": "",
// The tier ID the customer was upgraded from
"from_tier_id": "",
// The tier ID the customer was upgraded to
"to_tier_id": "",
// The name of the tier the customer was upgraded from
"from_tier_name": "Silver",
// The name of the tier the customer was upgraded to
"to_tier_name": "Gold",
// The level of the tier the customer was upgraded from
"from_tier_level": 1,
// The level of the tier the customer was upgraded to
"to_tier_level": 2,
// What triggered the tier change
"triggered_by": "EVENT", // or "CRON_DOWNGRADE" or "REVERSAL"
// Optional: The event ID that triggered the tier change (if triggered_by is "EVENT" or "REVERSAL")
"trigger_event_id": "",
// Optional: The event type that triggered the tier change (if triggered_by is "EVENT" or "REVERSAL")
"trigger_event_type": "PURCHASE"
}
}
```
### Tier downgrade
Triggers when a customer's tier is downgraded. This can happen during the cron job when a customer no longer meets the requirements to maintain their current tier, or when an event is reversed.
```json theme={null}
{
"event": "TIER_DOWNGRADE",
"data": {
"customer_id": "",
// The tier ID the customer was downgraded from
"from_tier_id": "",
// The tier ID the customer was downgraded to
"to_tier_id": "",
// The name of the tier the customer was downgraded from
"from_tier_name": "Gold",
// The name of the tier the customer was downgraded to
"to_tier_name": "Silver",
// The level of the tier the customer was downgraded from
"from_tier_level": 2,
// The level of the tier the customer was downgraded to
"to_tier_level": 1,
// What triggered the tier change
"triggered_by": "CRON_DOWNGRADE", // or "EVENT" or "REVERSAL"
// Optional: The event ID that triggered the tier change (if triggered_by is "EVENT" or "REVERSAL")
"trigger_event_id": "",
// Optional: The event type that triggered the tier change (if triggered_by is "EVENT" or "REVERSAL")
"trigger_event_type": "REVERSE"
}
}
```
## Related architecture
* [Registration flow](/resources/data-architecture#registration-flow) — `CUSTOMER_REGISTRATION` and profile lifecycle notifications
* [Activity & events](/resources/data-architecture#activity-events) — Reward, tier, and accumulation webhooks from the loyalty path
# Show customer data
Source: https://docs.masivo.ai/api-reference/guides/show-customer-data
Learn how to show your customers data using Masivo's REST API
Check the complete list of parameters and responses of the [`GET
customers`](/api-reference/customers/\[id]/get) endpoint to get customer data,
and the [`POST customer`](/api-reference/customers/post) endpoint to
register a new customer.
```typescript theme={null}
const response = await fetch("https://api.masivo.ai/customers", {
method: "GET",
headers: { Authorization: `Bearer ${accessToken}` }
});
```
### Using the customers data
When a customer wants to check their details in *Masivo*, you'll need to integrate a **GET** request to the [`customers endpoint`](/api-reference/customers/\[id]/get). This endpoint retrieves data such as their current tier and wallet balance.
This can be useful in scenarios like displaying a points dashboard, showing tier progression, or presenting reward balances within your ecommerce platform.
### Flow diagram example
# Submit reviews
Source: https://docs.masivo.ai/api-reference/guides/submit-reviews
Learn how to load a review form definition and submit reviews through Masivo's REST API
See the full parameter and response reference for [`GET review
form`](/api-reference/review-forms/\[id]/get), [`POST
review`](/api-reference/review-forms/\[id]/reviews/post), and [`GET
reviews`](/api-reference/reviews/get).
### Prerequisites
Authenticate with the Storefront API using a Bearer access token. See [Integrate with Masivo](/api-reference/guides/integrate-with-masivo).
These endpoints are available with **CLIENT** and **SERVER** API keys. CLIENT
keys are suitable for embedded apps that only need to read review form
definitions and submit reviews.
### 1. Load the active review form
Fetch the review form schema before rendering your UI:
```typescript theme={null}
const reviewFormId = "your-review-form-uuid";
const response = await fetch(
`https://app.masivo.ai/api/storefront/v1/review-forms/${reviewFormId}`,
{
headers: { Authorization: `Bearer ${accessToken}` }
}
);
const { data: reviewForm } = await response.json();
```
Only review forms with `status: "ACTIVE"` are returned. Inactive or deleted forms respond with `404`.
Use `reviewForm.fields` to build inputs. Each field has a `name` (API key), `label`, `type`, and optional `options`, `images`, `min`, and `max`.
### 2. Submit a review
Send answers keyed by **field name** (not field id):
```typescript theme={null}
const payload = {
customer_id: "unique-customer-id",
source: "app",
platform: "ios",
brand_id: "0001",
store_id: "store_1",
channel_id: "mobile",
answers: {
score: 4,
reason: ["wait_time"],
comment: "Good follow-up"
},
respondent: {
name: "Jane Doe"
},
metadata: {
trip_id: "trip_1"
}
};
const submit = await fetch(
`https://app.masivo.ai/api/storefront/v1/review-forms/${reviewFormId}/reviews`,
{
method: "POST",
headers: {
Authorization: `Bearer ${accessToken}`,
"Content-Type": "application/json"
},
body: JSON.stringify(payload)
}
);
const { data: review } = await submit.json();
```
On success you receive `201` with the created review, including `event_id` for the linked `REVIEW` event.
### Anonymous reviews
Omit `customer_id` when the respondent is not known:
```typescript theme={null}
const payload = {
source: "google_maps",
answers: {
score: 5
},
respondent: {
name: "Anonymous reviewer"
}
};
```
The review stores `customer_id: null`. The linked event uses the review id as the customer identifier.
### 3. List reviews (optional)
Paginate and filter stored reviews. Both `from` and `to` are required (zero-based, inclusive range):
```typescript theme={null}
const params = new URLSearchParams({
from: "0",
to: "20",
review_form_id: reviewFormId,
customer_id: "user_abc123",
source: "app",
platform: "ios",
brand_id: "0001",
store_id: "store_1",
channel_id: "mobile",
anonymous: "true",
created_from: "2026-07-01T00:00:00Z",
created_to: "2026-07-31T23:59:59Z"
});
const list = await fetch(
`https://app.masivo.ai/api/storefront/v1/reviews?${params}`,
{
headers: { Authorization: `Bearer ${accessToken}` }
}
);
const { count, from, to, data } = await list.json();
```
| Query param | Description |
| - | - |
| `from`, `to` | Required pagination indices (0-based) |
| `review_form_id` | Filter by review form UUID |
| `customer_id` | Filter by customer external id |
| `anonymous` | `"true"` returns only reviews with `customer_id: null` |
| `source`, `platform` | Attribution dimensions |
| `brand_id`, `store_id`, `channel_id` | Commerce scope filters |
| `created_from`, `created_to` | ISO timestamps for submission window |
### Common errors
| Status | Cause | Example `details` |
| - | - | - |
| `400` | Answer validation failed | `Field score is required` |
| `400` | Invalid option value | `Field reason must only include configured options` |
| `404` | Review form not found or inactive | — |
| `422` | Missing or invalid body fields | Zod issues (for example missing `source`) |
### Answer rules by field type
| Field type | `answers` value |
| - | - |
| `SCORE` | Number within `min`–`max` |
| `TEXT` | String |
| `SELECT` | String matching an option `value` |
| `MULTI_SELECT` | Array of strings, each matching an option `value` |
Required fields must be present and non-empty. Optional fields can be omitted.
### Related documentation
* [Review forms concept](/concepts/review-forms)
* [Create and manage review forms](/guides/create-and-manage-review-forms)
* [Customer events](/concepts/events) — includes `REVIEW` events
# Validate rewards redemption
Source: https://docs.masivo.ai/api-reference/guides/validate-rewards-redemption
Learn how to preview & validate rewards redemptions
Check the complete list of parameters and responses of the [`PUT
redeem/preview`](/api-reference/events/redeem/preview/put) endpoint to preview
or validate rewards redemptions.
## Overview
When you want to emit a *purchase event* that contains rewards redemptions, you may want 2 things before emitting the event:
1. **Preview the rewards redemptions**: This allows your customers to be sure what they are going to redeem before they actually redeem it.
2. **Validate the rewards redemptions**: This allows you to check if the rewards redemptions are valid and can be redeemed.
### Why do we need to check if a redemption is valid?
Every type of reward have different rules to follow in order to be redeemed.
**POINTS**: Can be redeemed at any level, this is the reward type that is most flexible.
**DISCOUNT**: Discounts when created are configured to work on a specific entity, like a product, the shipping cost, or the order value. When redeeming a discount, you need to set the discount at the same entity that was configured.
**PRODUCT**: Either ADD or REPLACE product rewards must only be redeemed at the order level. This is because adding a product that doesn't exist or replacing a product for another one, you wouldn't have a place to set the rewards to be redeemed.
**GIFT CARD**: Gift cards are redeemed using only the redeem gift card endpoints. Redeeming a gift card in a purchase event will not work.
Additionally, each reward may have some conditions configured to be met in order to be redeemed. For example, a discount may have a minimum order value to be redeemed.
## Preview rewards redemptions
To preview rewards redemptions, you need to send a `PUT` request to the [`redeem/preview`](/api-reference/events/redeem/preview/put) endpoint.
The endpoint will send back the exact order you originally sent, but with an additional keyword redemptions\_result at each level where a `redeem` array was found.
The endpoint doesn't want to force you how to present the data to the customer, it just provide the data you need to build whatever experience you want to show.
### Example Order sent
Let's pretend we want to preview the rewards redemptions for the following order:
```json theme={null}
{
"order": {
"products": [
{
"sku": "123",
"amount": 1,
"value": 10,
// Rewards to be redeemed at the product level
"redeem": [
{
"reward_id": "",
"amount": 6 // Assume a conversion factor of 1
}
]
}
],
"shipping": {
"value": 2
},
"value": 12
}
}
```
### Example Order received
The response will be the same order you sent, but with the rewards redemptions previewed:
```json theme={null}
{
"order": {
"products": [
{
"sku": "123",
"amount": 1,
"value": 10,
"redeem": [
{
"reward_id": "",
"amount": 6
}
],
"redemptions_result": {
"redeemed": [
{
"id": "",
"action": "UPDATE",
"amount": 6,
"reward": {
/* reward data */
}
}
],
"value": 4,
"amount": 1,
"discount_value": 6,
"discount_percent": 0.6,
"action": "UPDATE"
}
}
],
"shipping": {
"value": 2
},
"value": 12,
"redemptions_result": {
"redeemed": [],
"value": 6,
"amount": 1,
"discount_value": 6,
"discount_percent": 0.5,
"action": "UPDATE"
}
},
"redemption_steps": [
{
"reward_id": "",
"reward_amount": 6,
"target": "product",
"target_id": "123",
"action": "UPDATE",
"discount_value": 6,
"before": {
"value": 12,
"amount": 1,
"discount_value": 0,
"discount_percent": 0
},
"after": {
"value": 6,
"amount": 1,
"discount_value": 6,
"discount_percent": 0.5
},
"target_before": {
"value": 10,
"amount": 1,
"discount_value": 0,
"discount_percent": 0
},
"target_after": {
"value": 4,
"amount": 1,
"discount_value": 6,
"discount_percent": 0.6
}
}
]
}
```
Several things happened in the response:
1. The `redemptions_result` object was to the product that redeemed 6 points.
2. This new object contains the discounted value of the product `value`, how much was discounted `discount_value`, the percentage of the discount `discount_percent`. This values can be used to show the customer how much they are saving.
3. It also contains an `action` field that tells you what to do with the affected entity:
* `UPDATE`: The entity was updated, display these values instead.
* `READ`: The entity has been updated before, but the last redeem didn't change anything.
* `DELETE`: Then entity was removed, don't display this item in the order.
* `CREATE`: The entity is new and must be display along the other products in the order.
4. The `redeemed` array contains the rewards that were redeemed and the action that was taken.
5. Even though we redeemed a reward at the product level, a redemption\_result was created at the order level as well. This is because the order level was affected by the redemption.
6. `redemption_steps` records every redemption in application order. `before` and `after` show how the order changed. Product and shipping steps also include `target_before` and `target_after` for the affected item.
### How `value`, `discount_value` and `discount_percent` are calculated
Rewards are applied one by one, in the same order as the `redeem` array. Each redemption builds on top of the previous `redemptions_result` of the entity, so the fields always accumulate across all redeemed rewards:
* **POINTS**: discounts `amount × conversion_factor` from the entity `value`. The accumulated discount is capped at the entity value, so `value` never goes negative.
* **DISCOUNT (fixed)**: discounts `discount_value × amount` from the entity `value`.
* **DISCOUNT (percentage)**: the percentage is applied over the *remaining* `value` (after previous redemptions). This means the order of the `redeem` array matters for percentage discounts: a 50% discount applied after a \$2 discount is smaller than one applied before it.
* **PRODUCT (ADD\_PRODUCT / REPLACE\_PRODUCT)**: adds `estimated_value × replace_amount` (or `new_amount` for additions) to `discount_value`, but does **not** reduce the entity `value`. The saving is an estimate: the customer pays the same for the rest of the order and receives the added/replaced product for free.
This is what each field means after all redemptions are applied:
* `value`: what the entity costs after applying the value-reducing rewards (POINTS and DISCOUNT). This is the field to use when [validating the amount to charge](#validate-rewards-redemptions).
* `discount_value`: the total accumulated saving from **all** redeemed rewards, including the estimated value of PRODUCT rewards. Because PRODUCT rewards don't reduce `value`, `value + discount_value` can be greater than the original entity value.
* `discount_percent`: the percentage that the value-reducing rewards represent over the original entity value, capped at `1`. PRODUCT rewards don't change it (it keeps the previous percentage, or `null` if there is none), so when PRODUCT rewards are redeemed `discount_percent` will not equal `discount_value` divided by the original value.
* `redemption_steps[].discount_value`: the saving contributed by that individual redemption to the order total. Failed or fully capped redemptions contribute `0`.
* `redemption_steps[].before.discount_percent` and `redemption_steps[].after.discount_percent`: the cumulative saving divided by the original order value, capped at `1`. These step percentages include the estimated value of PRODUCT rewards.
`redemption_steps` is chronological across the whole preview: product redemptions are applied first in product order, then shipping redemptions, and finally order redemptions. Within each target, entries follow the original `redeem` array order. Use `target` to map the contribution to an external code—for example, `shipping` to a delivery discount code and `order` or `product` to the corresponding merchandise discount code. `before` and `after` always show the cumulative order state. `target_before` and `target_after` are only present for product and shipping steps, where the affected target differs from the order.
For example, on a $106.25 order redeeming a 50% order discount plus a REPLACE_PRODUCT reward with an `estimated_value` of $2.34, the existing order-level result remains `value: 53.125`, `discount_value: 55.465` and `discount_percent: 0.5`. In `redemption_steps`, the first reward leaves `after.discount_percent` at `0.5` and the product reward raises it to `0.5220235294117647` (`55.465 / 106.25`).
### Example Order with an error
Some rewards may have conditions that need to be met in order to be redeemed. If the conditions are not met, the rewards will not be redeemed. Take into consideration that the response will end with a status of 200. You will need to display the error to the customer.
```json theme={null}
{
"order": {
"products": [
{
"sku": "123",
"amount": 1,
"value": 10,
"redeem": [
{
"reward_id": "",
"amount": 6
}
],
"redemptions_result": {
"redeemed": [
{
"id": "",
"action": "READ",
"amount": 1,
"reward": {
/* reward data */
},
"error": "Cannot redeem a shipping discount on a product"
}
],
"value": 10,
"amount": 1,
"discount_value": 0,
"discount_percent": 0,
"action": "READ"
}
}
],
"shipping": {
"value": 2
},
"value": 12
},
"redemption_steps": [
{
"reward_id": "",
"reward_amount": 6,
"target": "product",
"target_id": "123",
"action": "READ",
"discount_value": 0,
"before": {
"value": 12,
"amount": 1,
"discount_value": 0,
"discount_percent": 0
},
"after": {
"value": 12,
"amount": 1,
"discount_value": 0,
"discount_percent": 0
},
"target_before": {
"value": 10,
"amount": 1,
"discount_value": 0,
"discount_percent": 0
},
"target_after": {
"value": 10,
"amount": 1,
"discount_value": 0,
"discount_percent": 0
},
"error": "Cannot redeem a shipping discount on a product"
}
]
}
```
In this case, the discount was not redeemed because it was a shipping discount and it was redeemed at the product level.
A new field `error` was added to the redeemed object. This error can either be shown to the customer or used to debug your implementation.
### Re-validating an order
Many things may happen that will force you to update an order. For example: adding/removing products, applying/removing a reward, updating an item amount, changing the delivery address, etc. After each of these situations, you will need to re-validate the order.
When revalidating the order you **must keep a copy of your original products and values**. Do not replace them with the 'redemptions\_result' object. This data should only be used to help your customers preview the rewards redemptions. Use the action field to know what to do with the entity.
If you are re-validating an order, it is super important to also bring with you the 'redemptions\_result' object of each entity. Masivo will use these objects to know what rewards to redeem next, which rewards were already redeemed, and which rewards were removed.
## Validate rewards redemptions
The same endpoint can be repurpose to validate an order. Call this endpoint in your backend and use the field `order.redemptions_result.value` to compare if what you are going to charge the customer matches.
If the order values don't match, the order may have been tampered with. You should not emit the event.
Masivo also checks if all conditions match for a redemption when an event is
emitted. If the conditions don't match, or whether the customer has
insufficient funds, the event will be rejected.
# Get in-app capabilities
Source: https://docs.masivo.ai/api-reference/inapp/capabilities/get
GET /inapp/capabilities
Check whether in-app messaging is enabled for the account and which journey event types can trigger real-time in-app delivery.
# Schedule audience push
Source: https://docs.masivo.ai/api-reference/push/audiences/[id]/schedule/post
POST /push/audiences/{id}/schedule
Schedule a push notification for a push-eligible audience using an active push template. The existing notification cron sends it when scheduled_date is due. Authenticate with a Bearer access token from /auth/authorize (not x-api-key).
Use a **Bearer** access token from [`/auth/authorize`](/api-reference/auth/authorize/get). Do not send `x-api-key` on this endpoint — that header is only for authorize.
# Send customer push
Source: https://docs.masivo.ai/api-reference/push/customers/[id]/send/post
POST /push/customers/{id}/send
Send one push notification to one active customer using an active push template. This sends synchronously and does not create delivery history. Authenticate with a Bearer access token from /auth/authorize (not x-api-key).
Use a **Bearer** access token from [`/auth/authorize`](/api-reference/auth/authorize/get). Do not send `x-api-key` on this endpoint — that header is only for authorize.
# Resource limits
Source: https://docs.masivo.ai/api-reference/resources/resource-limits
Learn about resource limits in Masivo and how it applies to different environments and endpoints
## API Overview
Masivo implements rate limiting to ensure a stable and reliable API service for all customers. Rate limiting prevents abuse, guarantees fair usage, and helps maintain overall platform performance.
There are two main rate limiters in place:
1. **General Rate Limiter:** Applies to all endpoints, protecting the system from excessive traffic and potential misuse.
2. **Migration Rate Limiter:** Specifically applies to migration endpoints, which often handle large batch operations. This limiter ensures that migration processes do not overwhelm the system and are executed in a controlled manner.
### Environment Differences
Every new account starts as a DEV account, which comes with certain limitations. In this environment, users can perform their integration tests for free. Once testing is complete, they can switch to PROD, keeping in mind that Masivo will start accumulating usage.
**To switch to PROD, please contact the support team.**
Below is a comparison of the rate limits for both environments:
| Environment | General Rate Limiter | Migration Rate Limiter |
| - | - | - |
| Dev | 2500 requests per day | 1 request every 5 seconds and up to 250 requests per hour |
| Prod | Unlimited requests | 15 requests every 5 seconds and up to 10,000 requests per hour |
Migration limits are tracked independently for each endpoint. For example,
requests to customer migration do not consume the contact migration quota.
### Adjusting Your Requests
If you experience throttling or delays due to rate limits, spread your API calls over time rather than sending a burst of requests all at once.
Regularly monitor your request usage and adjust your application logic
accordingly to avoid reaching the rate limits.
### Rate Limit Headers
When making requests to the API, you'll receive the following headers in the response that provide information about your current rate limit status:
| Header Name | Description |
| - | - |
| X-RateLimit-Limit | The maximum number of requests you're permitted to make in the current limit window |
| X-RateLimit-Remaining | The number of requests remaining in the current rate limit window |
| X-RateLimit-Reset | The time in seconds until the current rate limit window resets |
| Retry-After | The time in seconds to wait before making another request (only present on 429 responses) |
When you exceed the rate limit, you'll receive a `429 Too Many Requests` response code. In this case, the `Retry-After` header will indicate how long you should wait before retrying your request.
## General limits
| **Entity** | **Max Amount** |
| - | -: |
| **Brands** | 200 |
| **Campaigns** | 1000 |
| **Channels** | 50 |
| **Custom Event Types** | 50 |
| **Event Tags** | 50 |
| **Products** | 1000 |
| **Rewards** | 1000 |
| **Stores** | 1000 |
| **Tiers** | 10 |
# Get form
Source: https://docs.masivo.ai/api-reference/review-forms/[id]/get
GET /review-forms/{id}
Get an active review form definition for rendering in your app. Only review forms with status ACTIVE and not soft-deleted are returned.
# Create review
Source: https://docs.masivo.ai/api-reference/review-forms/[id]/reviews/post
POST /review-forms/{id}/reviews
Submit a review. Answers must be keyed by field name. Creates a REVIEW event and a reviews row. Rolls back the event if the insert fails.
# List reviews
Source: https://docs.masivo.ai/api-reference/reviews/get
GET /reviews
List and filter reviews for the account.
# List ticket pipelines
Source: https://docs.masivo.ai/api-reference/ticket-pipelines/get
GET /ticket-pipelines
List active ticket pipelines and their ordered stages. Use these internal ids and stage codes when creating or moving tickets.
# Remove ticket assignee
Source: https://docs.masivo.ai/api-reference/tickets/[id]/assignees/delete
DELETE /tickets/{id}/assignees
Remove an assignee from a ticket. Marks the assignment record as removed and emits a TICKET_ASSIGNEE_REMOVED event. No-op when the user was not assigned.
# List ticket assignees
Source: https://docs.masivo.ai/api-reference/tickets/[id]/assignees/get
GET /tickets/{id}/assignees
List the active assignee records for a ticket.
# Add ticket assignee
Source: https://docs.masivo.ai/api-reference/tickets/[id]/assignees/post
POST /tickets/{id}/assignees
Add an account member as an assignee (or watcher) to a ticket. Idempotent: re-adding an existing assignee is a no-op. Emits a TICKET_ASSIGNEE_ADDED event.
# Get ticket
Source: https://docs.masivo.ai/api-reference/tickets/[id]/get
GET /tickets/{id}
Get a ticket by id for the account.
# Update ticket
Source: https://docs.masivo.ai/api-reference/tickets/[id]/patch
PATCH /tickets/{id}
Update a ticket title, description, priority, assignees, or stage in one request. Use add_assignee_user_id, remove_assignee_user_id, or assignee_user_ids to manage assignees. Stage changes validate required stage fields.
# Create ticket
Source: https://docs.masivo.ai/api-reference/tickets/post
POST /tickets
Create a ticket manually, from a review, or for an external customer association. If review_id is provided the ticket is created with source_type review and deduplicated against active review tickets.
# Account transactions
Source: https://docs.masivo.ai/api-reference/transactions/get
GET /transactions
Get account transactions by date. Maximum 1000 rows per request. Without the cursor query parameter, the response is the first 1000 records. Paginate with optional cursor.