diff --git a/content/docs/images/integrations-customer-io.jpeg b/content/docs/images/integrations-customer-io.jpeg new file mode 100644 index 0000000..eda6008 Binary files /dev/null and b/content/docs/images/integrations-customer-io.jpeg differ diff --git a/content/docs/images/integrations-discord.jpeg b/content/docs/images/integrations-discord.jpeg new file mode 100644 index 0000000..0e1ca42 Binary files /dev/null and b/content/docs/images/integrations-discord.jpeg differ diff --git a/content/docs/images/integrations-facebook-pixel.jpeg b/content/docs/images/integrations-facebook-pixel.jpeg new file mode 100644 index 0000000..d89b61d Binary files /dev/null and b/content/docs/images/integrations-facebook-pixel.jpeg differ diff --git a/content/docs/integrations/customer-io.mdx b/content/docs/integrations/customer-io.mdx new file mode 100644 index 0000000..4c190be --- /dev/null +++ b/content/docs/integrations/customer-io.mdx @@ -0,0 +1,405 @@ +--- +title: "Customer.io" +description: "The Customer.io integration sends subscription lifecycle events from Superwall to Customer.io's Data Pipelines API. This enables you to trigger targeted messaging campaigns, build user segments based on subscription behavior, and track the complete customer journey from trial to paid subscriber." +--- + +In the **Communication** section within **Integrations**, you can connect your Customer.io account to Superwall: + +![](../images/integrations-customer-io.jpeg) + +## Features + +- **Real-time Event Tracking**: Subscription events are sent immediately to Customer.io as they occur +- **Multi-Region Support**: Choose between US and EU data residency to comply with data regulations +- **Flexible Revenue Reporting**: Report either gross revenue or net proceeds after store fees +- **Sandbox Environment Support**: Separate API key for testing without polluting production data +- **Anonymous User Handling**: Configurable behavior for users without an identified app user ID +- **Custom Event Names**: Remap default event names to match your existing Customer.io conventions +- **Automatic User Identification**: Smart routing between `userId` and `anonymousId` based on user state + +## Configuration + +### Required Settings + +| Field | Description | Example | +|-------|-------------|---------| +| `integration_id` | Must be set to `"customerio"` | `"customerio"` | +| `region` | Data residency region for your Customer.io workspace | `"US"` or `"EU"` | +| `api_key` | Pipelines API key from your HTTP source | `"abc123def456..."` | +| `sales_reporting` | Whether to report gross Revenue or net Proceeds | `"Revenue"` or `"Proceeds"` | + +### Optional Settings + +| Field | Description | Default | +|-------|-------------|---------| +| `sandbox_api_key` | Separate Pipelines API key for sandbox/test events | None (sandbox events skipped) | +| `anonymous_user_behavior` | How to handle events from users without an app user ID | `"send"` | +| `eventNameMappings` | Custom mapping to rename default event names | None | + +### Example Configuration + +```json +{ + "integration_id": "customerio", + "region": "US", + "api_key": "your-pipelines-api-key", + "sales_reporting": "Revenue", + "sandbox_api_key": "your-sandbox-pipelines-api-key", + "anonymous_user_behavior": "send", + "eventNameMappings": { + "sw_trial_start": "trial_started", + "sw_subscription_start": "subscription_started", + "sw_renewal": "subscription_renewed" + } +} +``` + +## Getting Your API Key + +The Customer.io integration uses the **Pipelines API** (part of Customer.io Data Pipelines), not the Track API. To get your API key: + +1. Log in to your Customer.io account +2. Navigate to **Data Pipelines** in the left sidebar +3. Go to **Sources** +4. Click **Add Source** and select **HTTP** +5. Name your source (e.g., "Superwall Events") +6. Copy the **API Key** displayed after creation + +**Important**: The Pipelines API key is different from the Track API credentials (Site ID + API Key). Make sure you're using the correct key from Data Pipelines. + +## Event Mapping + +Superwall subscription events are transformed into Customer.io events based on the event type and subscription period. All events are prefixed with `sw_` by default. + +### Trial Events + +| Superwall Event | Condition | Customer.io Event | +|-----------------|-----------|-------------------| +| `INITIAL_PURCHASE` | `periodType = Trial` | `sw_trial_start` | +| `CANCELLATION` | `periodType = Trial` | `sw_trial_cancelled` | +| `UNCANCELLATION` | `periodType = Trial` | `sw_trial_uncancelled` | +| `EXPIRATION` | `periodType = Trial` | `sw_trial_expired` | +| `RENEWAL` | `periodType = Trial` | `sw_trial_converted` | + +### Intro Offer Events + +| Superwall Event | Condition | Customer.io Event | +|-----------------|-----------|-------------------| +| `INITIAL_PURCHASE` | `periodType = Intro` | `sw_intro_offer_start` | +| `CANCELLATION` | `periodType = Intro` | `sw_intro_offer_cancelled` | +| `UNCANCELLATION` | `periodType = Intro` | `sw_intro_offer_uncancelled` | +| `EXPIRATION` | `periodType = Intro` | `sw_intro_offer_expired` | +| `RENEWAL` | `periodType = Intro` | `sw_intro_offer_converted` | + +### Subscription Events + +| Superwall Event | Condition | Customer.io Event | +|-----------------|-----------|-------------------| +| `INITIAL_PURCHASE` | `periodType = Normal` | `sw_subscription_start` | +| `RENEWAL` | `periodType = Normal` | `sw_renewal` | +| `RENEWAL` | `isTrialConversion = true` | `sw_trial_converted` | +| `CANCELLATION` | `periodType = Normal` | `sw_subscription_cancelled` | +| `UNCANCELLATION` | `periodType = Normal` | `sw_subscription_uncancelled` | +| `EXPIRATION` | `periodType = Normal` | `sw_subscription_expired` | + +### Other Events + +| Superwall Event | Customer.io Event | +|-----------------|-------------------| +| `PRODUCT_CHANGE` | `sw_product_change` | +| `BILLING_ISSUE` | `sw_billing_issue` | +| `SUBSCRIPTION_PAUSED` | `sw_subscription_paused` | +| `NON_RENEWING_PURCHASE` | `sw_non_renewing_purchase` | +| Any event with `price < 0` | `sw_refund` | + +## Event Properties + +Each event sent to Customer.io includes comprehensive properties from the original Superwall event, plus additional formatted fields for revenue tracking. + +### Standard Properties + +All events include the complete Superwall event data: + +| Property | Description | Example | +|----------|-------------|---------| +| `id` | Unique event identifier | `"evt_abc123"` | +| `productId` | The subscription product ID | `"com.app.premium.monthly"` | +| `store` | App store (APP_STORE, PLAY_STORE) | `"APP_STORE"` | +| `environment` | Production or Sandbox | `"Production"` | +| `countryCode` | User's country code | `"US"` | +| `currencyCode` | Transaction currency | `"USD"` | +| `originalAppUserId` | Your app's user identifier | `"user_12345"` | +| `originalTransactionId` | Store's original transaction ID | `"1000000123456789"` | +| `transactionId` | Current transaction ID | `"1000000987654321"` | +| `purchasedAt` | Purchase timestamp (ms) | `1705312200000` | +| `expirationAt` | Subscription expiration (ms) | `1707990600000` | +| `periodType` | Trial, Intro, or Normal | `"Normal"` | +| `isTrialConversion` | Whether this converts a trial | `true` | +| `isFamilyShare` | Family sharing purchase | `false` | +| `bundleId` | App bundle identifier | `"com.example.app"` | + +### Revenue Properties + +When the event has a non-zero price, these additional properties are included: + +| Property | Description | Example | +|----------|-------------|---------| +| `price` | Amount based on `sales_reporting` setting | `9.99` | +| `currency` | Currency code | `"USD"` | +| `product_id` | Product identifier | `"com.app.premium.monthly"` | +| `subscription_id` | Original transaction ID | `"1000000123456789"` | +| `offer_code` | Promotional offer code (if present) | `"SUMMER2024"` | + +### Revenue vs Proceeds + +The `sales_reporting` setting controls which amount is sent: + +- **Revenue**: The full price charged to the customer (e.g., $9.99) +- **Proceeds**: The amount after store fees are deducted (e.g., $8.49 after Apple's 15-30% commission) + +## User Identification + +Customer.io uses either `userId` or `anonymousId` to identify users. The integration automatically selects the appropriate identifier based on user state. + +### Known Users + +For users with an `originalAppUserId` set in Superwall: + +```json +{ + "userId": "user_12345", + "event": "sw_subscription_start", + "properties": { ... }, + "timestamp": "2024-01-15T10:30:00.000Z" +} +``` + +### Anonymous Users + +For users without an `originalAppUserId`, the behavior depends on `anonymous_user_behavior`: + +**When set to `"send"` (default)**: +- Events are sent with an `anonymousId` constructed from the store and transaction ID +- Format: `$STORE_NAME:originalTransactionId` + +```json +{ + "anonymousId": "$APP_STORE:1000000123456789", + "event": "sw_subscription_start", + "properties": { ... }, + "timestamp": "2024-01-15T10:30:00.000Z" +} +``` + +**When set to `"dontSend"`**: +- Events from anonymous users are skipped entirely +- Useful if you only want to track identified users + +## Sandbox Handling + +The integration supports separate handling for sandbox (test) events: + +### With Sandbox API Key Configured + +When `sandbox_api_key` is provided: +- Production events use the main `api_key` +- Sandbox events use the `sandbox_api_key` +- Both are sent to Customer.io but can be routed to different destinations + +### Without Sandbox API Key + +When `sandbox_api_key` is not provided: +- Production events are sent normally +- Sandbox events are **skipped entirely** +- This prevents test data from polluting your production Customer.io workspace + +## Data Residency + +Customer.io offers data residency in two regions. The integration automatically routes to the correct endpoint: + +| Region | API Endpoint | +|--------|--------------| +| US | `https://cdp.customer.io/v1/track` | +| EU | `https://cdp-eu.customer.io/v1/track` | + +Choose the region that matches your Customer.io workspace configuration. Using the wrong region will result in authentication errors. + +## Custom Event Names + +Use `eventNameMappings` to rename default event names to match your existing Customer.io conventions: + +```json +{ + "eventNameMappings": { + "sw_trial_start": "Started Free Trial", + "sw_subscription_start": "Subscribed", + "sw_renewal": "Subscription Renewed", + "sw_subscription_cancelled": "Subscription Cancelled", + "sw_refund": "Refund Processed" + } +} +``` + +Only events you specify in the mapping are renamed. All other events keep their default `sw_` prefixed names. + +## Testing the Integration + +### 1. Validate Credentials + +The integration validates credentials by sending a test event to Customer.io. If the API key is invalid or the region is incorrect, you'll receive an authentication error. + +### 2. Verify in Customer.io + +After sending test events: +1. Go to **Data Pipelines** → **Sources** → your HTTP source +2. Click on **Events** to see incoming events +3. Verify event names and properties match expectations + +### 3. Test Scenarios + +Verify these scenarios work correctly: + +- [ ] Production event with known user (should use `userId`) +- [ ] Production event with anonymous user (should use `anonymousId` or skip) +- [ ] Sandbox event with sandbox API key (should send to Customer.io) +- [ ] Sandbox event without sandbox API key (should be skipped) +- [ ] Event with custom name mapping (should use remapped name) +- [ ] Revenue event (should include `price`, `currency`, `product_id`) +- [ ] Non-revenue event like cancellation (should not include revenue properties) + +## Best Practices + +1. **Use separate sandbox credentials**: Configure a `sandbox_api_key` to keep test data separate from production, or leave it blank to skip sandbox events entirely. + +2. **Choose the right sales reporting**: Use "Revenue" for customer-facing metrics and "Proceeds" for financial reporting that accounts for store fees. + +3. **Handle anonymous users thoughtfully**: If your app requires login, use `"dontSend"` to avoid cluttering Customer.io with unidentifiable users. + +4. **Keep event names consistent**: If you have existing events in Customer.io, use `eventNameMappings` to maintain naming consistency across your data. + +5. **Verify your region**: Ensure your `region` setting matches your Customer.io workspace location to avoid authentication failures. + +6. **Test with sandbox first**: Always test your integration configuration with sandbox events before going live with production data. + +## Common Use Cases + +### Win-Back Campaigns + +Trigger automated campaigns when users cancel: +1. Listen for `sw_subscription_cancelled` events +2. Create a segment of recently cancelled users +3. Send a series of win-back emails with special offers + +### Trial Conversion Optimization + +Improve trial-to-paid conversion: +1. Track `sw_trial_start` to begin a nurture sequence +2. Send educational content about premium features +3. Trigger a special offer before trial expiration +4. Track `sw_trial_converted` to measure success + +### Churn Prevention + +Identify and engage at-risk subscribers: +1. Monitor `sw_billing_issue` events +2. Send immediate notification to update payment method +3. Follow up with helpful support content +4. Track resolution with subsequent `sw_renewal` events + +### Revenue Analytics + +Build comprehensive revenue reporting: +1. Segment users by subscription status +2. Track lifetime value using revenue properties +3. Analyze conversion rates by cohort +4. Measure impact of promotional offers via `offer_code` + +## Troubleshooting + +### Events Not Appearing in Customer.io + +**Possible causes:** +- Incorrect API key (make sure you're using Pipelines API key, not Track API) +- Wrong region selected (US vs EU mismatch) +- Sandbox events without sandbox API key configured (events are skipped) +- Anonymous users with `dontSend` behavior (events are skipped) + +**Solution:** Verify your API key is from Data Pipelines → Sources → HTTP, and check that your region matches your workspace. + +### Authentication Errors + +**Possible causes:** +- Using Track API credentials instead of Pipelines API key +- Region mismatch between configuration and Customer.io workspace +- API key has been revoked or regenerated + +**Solution:** Generate a new HTTP source in Data Pipelines and use the fresh API key. + +### Missing Revenue Properties + +**Possible causes:** +- Event has zero price (cancellations, expirations) +- Refund events (price is negative, still included but as negative value) + +**Solution:** Revenue properties (`price`, `currency`, `product_id`, `subscription_id`) are only added when the price is non-zero. This is expected behavior. + +### Wrong Event Names + +**Possible causes:** +- Event name mappings not configured +- Typo in mapping configuration + +**Solution:** Check your `eventNameMappings` configuration. Keys should be the default event names (e.g., `sw_trial_start`), and values should be your desired custom names. + +## Rate Limits + +Customer.io's Pipelines API has generous rate limits suitable for high-volume event ingestion: + +- **Requests**: 500 requests per second per source +- **Payload size**: 32KB per request + +The integration sends one event per webhook, well within these limits. If you experience rate limiting, contact Customer.io support to increase your limits. + +## API Reference + +### Endpoint + +``` +POST https://cdp.customer.io/v1/track (US region) +POST https://cdp-eu.customer.io/v1/track (EU region) +``` + +### Authentication + +Basic Authentication with the Pipelines API key as username and empty password: + +``` +Authorization: Basic base64(api_key:) +``` + +### Request Format + +```json +{ + "userId": "user_12345", + "event": "sw_subscription_start", + "timestamp": "2024-01-15T10:30:00.000Z", + "properties": { + "productId": "com.app.premium.monthly", + "price": 9.99, + "currency": "USD", + "store": "APP_STORE", + "environment": "Production", + ... + } +} +``` + +### Response + +Success: `200 OK` with empty body or acknowledgment + +Errors: +- `401 Unauthorized`: Invalid API key or wrong region +- `400 Bad Request`: Malformed request body +- `429 Too Many Requests`: Rate limit exceeded diff --git a/content/docs/integrations/discord.mdx b/content/docs/integrations/discord.mdx new file mode 100644 index 0000000..b5e5c4c --- /dev/null +++ b/content/docs/integrations/discord.mdx @@ -0,0 +1,480 @@ +--- +title: "Discord" +description: "The Discord integration sends real-time subscription notifications to your Discord channels via webhooks. Get instant visibility into subscription activity with beautifully formatted, color-coded embed messages that make it easy to monitor revenue, track trials, and respond to billing issues as they happen." +--- + +In the **Communication** section within **Integrations**, you can connect your Discord account to Superwall: + +![](../images/integrations-discord.jpeg) + +## Features + +- **Rich Embed Messages**: Beautiful, color-coded notifications with emoji indicators for quick visual parsing +- **Event Type Filtering**: Choose between revenue-only events or all subscription lifecycle events +- **Real-Time Notifications**: Instant alerts when subscription events occur +- **Smart Formatting**: Currency formatting, country names, and human-readable descriptions +- **Flexible Revenue Reporting**: Report either gross revenue or net proceeds after store fees +- **Anonymous User Handling**: Configurable behavior for users without an identified app user ID +- **Custom Event Names**: Override default event titles to match your team's terminology +- **Sandbox Indicators**: Clear visual badge when events come from test environments + +## Configuration + +### Required Settings + +| Field | Description | Example | +|-------|-------------|---------| +| `integration_id` | Must be set to `"discord"` | `"discord"` | +| `webhook_url` | Discord webhook URL from your server | `"https://discord.com/api/webhooks/123/abc..."` | +| `sales_reporting` | Whether to report gross Revenue or net Proceeds | `"Revenue"` or `"Proceeds"` | + +### Optional Settings + +| Field | Description | Default | +|-------|-------------|---------| +| `event_type` | Filter which events to send | `"All Subscription Events"` | +| `anonymous_user_behavior` | How to handle events from users without an app user ID | `"send"` | +| `eventNameMappings` | Custom mapping to rename default event titles | None | + +### Example Configuration + +```json +{ + "integration_id": "discord", + "webhook_url": "https://discord.com/api/webhooks/1234567890/abcdefghijklmnop", + "sales_reporting": "Revenue", + "event_type": "All Subscription Events", + "anonymous_user_behavior": "send", + "eventNameMappings": { + "sw_subscription_start": "New Premium Member!", + "sw_trial_start": "New Trial Started" + } +} +``` + +## Creating a Discord Webhook + +1. Open your Discord server +2. Go to **Server Settings** (click the server name → Settings) +3. Navigate to **Integrations** → **Webhooks** +4. Click **New Webhook** +5. Configure the webhook: + - **Name**: Choose a name (e.g., "Superwall Events") + - **Channel**: Select the channel where notifications will appear + - **Avatar**: Optionally customize the webhook's avatar +6. Click **Copy Webhook URL** +7. Paste the URL into your integration configuration + +**Tip**: Create a dedicated channel like `#subscription-events` or `#revenue-alerts` to keep notifications organized. + +## Event Filtering + +The `event_type` setting controls which events are sent to Discord: + +### All Subscription Events (Default) + +Sends every subscription lifecycle event: +- Trial starts and conversions +- New subscriptions +- Renewals +- Cancellations and expirations +- Billing issues +- Product changes +- Refunds + +**Best for**: Teams that want complete visibility into all subscription activity. + +### Revenue Events Only + +Only sends events with non-zero revenue: +- New paid subscriptions +- Renewals +- Trial conversions +- One-time purchases +- Refunds (negative revenue) + +**Skips**: Trial starts, cancellations, expirations, billing issues (unless they have revenue attached). + +**Best for**: Teams focused on revenue notifications without the noise of non-revenue events. + +## Message Format + +Discord messages are sent as rich embeds with the following structure: + +### Embed Structure + +``` +┌─────────────────────────────────────────┐ +│ [Superwall Logo] Superwall │ ← Author +├─────────────────────────────────────────┤ +│ 💰 New Subscriber │ ← Title (with emoji) +│ │ +│ $9.99 subscription started from │ ← Description +│ United States │ +├─────────────────────────────────────────┤ +│ 👤 User 🎯 Product 📱 Store │ ← Fields (inline) +│ user_123 com.app.pro App Store • │ +│ United States │ +│ │ +│ 💰 Revenue │ ← Revenue field +│ $9.99 │ +├─────────────────────────────────────────┤ +│ Powered by Superwall Jan 15, 2024 │ ← Footer + Timestamp +└─────────────────────────────────────────┘ +``` + +### Embed Fields + +| Field | Description | When Shown | +|-------|-------------|------------| +| 👤 User | User ID or "Anonymous" | Always | +| 🎯 Product | Product identifier | Always | +| 📱 Store | Store name and country | Always | +| 💰 Revenue / 💵 Proceeds | Formatted amount | When price ≠ 0 | +| ⚙️ Sandbox | Test environment indicator | Sandbox events only | +| 🎁 Offer | Promotional offer code | When offer code present | +| 🔄 Product Change | Old → New product | Product change events | + +## Event Titles and Colors + +Each event type has a distinct emoji, title, and color for quick visual identification. + +### Color Coding + +| Color | Hex Code | Meaning | +|-------|----------|---------| +| Green | `#36A64F` | Revenue events (purchases, renewals, conversions) | +| Blue | `#3498DB` | Trial events (non-revenue) | +| Red | `#FA6A6A` | Negative events (cancellations, refunds, expirations) | +| Orange | `#FF9500` | Billing issues | +| Purple | `#9B59B6` | Product changes | +| Gray | `#666666` | Other events | + +### Event Title Reference + +#### Trial Events +| Event | Title | Color | +|-------|-------|-------| +| Trial Start | 🤩 Trial Start | Blue | +| Trial Conversion | 💰 Trial Conversion | Green | +| Trial Cancelled | 😞 Cancelled Trial | Red | +| Trial Refunded | 🤬 Refunded Trial | Red | +| Trial Expired | 😞 Expired Trial | Red | +| Trial Uncancelled | 🤩 Trial Uncancelled | Blue | + +#### Intro Offer Events +| Event | Title | Color | +|-------|-------|-------| +| Intro Start (free) | 🤩 Intro Offer Start | Blue | +| Intro Start (paid) | 💰 Intro Offer Start | Green | +| Intro Conversion | 💰 Intro Offer Conversion | Green | +| Intro Cancelled | 😞 Cancelled Intro Offer | Red | +| Intro Refunded | 🤬 Refunded Intro Offer | Red | + +#### Subscription Events +| Event | Title | Color | +|-------|-------|-------| +| New Subscription | 💰 New Subscriber | Green | +| Renewal | 💰 Renewal | Green | +| Cancellation | 😞 Cancelled Subscription | Red | +| Refund | 🤬 Refunded Subscription | Red | +| Expiration | 😞 Expired Subscription | Red | +| Uncancellation | 🤩 Subscription Uncancelled | Green | + +#### Other Events +| Event | Title | Color | +|-------|-------|-------| +| One-Time Purchase | 💰 One-Time Purchase | Green | +| Product Change | 😵‍💫 Product Change | Purple | +| Billing Issue | 🫠 Billing Issue | Orange | +| Subscription Paused | ⏸️ Subscription Paused | Gray | + +## Revenue Display + +### Revenue vs Proceeds + +The `sales_reporting` setting controls which amount is displayed: + +- **Revenue**: The full price charged to the customer (e.g., $9.99) +- **Proceeds**: The amount after store fees (e.g., $8.49 after Apple's 15-30% commission) + +The field label changes based on your setting: +- Revenue mode: "💰 Revenue" +- Proceeds mode: "💵 Proceeds" + +### Currency Formatting + +Amounts are automatically formatted with the correct currency symbol and locale: +- `$9.99` for USD +- `€9.99` for EUR +- `£9.99` for GBP +- `¥999` for JPY + +### Zero-Value Events + +Events without revenue (trial starts, cancellations, expirations) do not show a revenue field, keeping the message compact. + +### Refunds + +Refunds display negative amounts: +- "💰 Revenue: -$9.99" + +## Anonymous User Handling + +The `anonymous_user_behavior` setting controls how events from unidentified users are handled: + +### Send (Default) + +- Events from anonymous users are sent to Discord +- User field displays "Anonymous" +- Useful for complete visibility into all subscription activity + +### Don't Send + +- Events from anonymous users are skipped +- No notification is sent to Discord +- Useful if you only want to track identified users + +## Sandbox Events + +Events from sandbox/test environments are clearly marked: + +- A "⚙️ Sandbox" field is added with value "Test Environment" +- Helps distinguish test events from production activity +- Production events do not show any environment indicator + +## Custom Event Names + +Use `eventNameMappings` to customize event titles: + +```json +{ + "eventNameMappings": { + "sw_trial_start": "🎉 New Trial User!", + "sw_subscription_start": "💎 VIP Member Joined", + "sw_renewal": "🔄 Subscription Renewed", + "sw_subscription_cancelled": "👋 Member Churned" + } +} +``` + +### Available Event Keys + +| Key | Default Title | +|-----|---------------| +| `sw_trial_start` | 🤩 Trial Start | +| `sw_trial_converted` | 💰 Trial Conversion | +| `sw_trial_cancelled` | 😞 Cancelled Trial | +| `sw_subscription_start` | 💰 New Subscriber | +| `sw_renewal` | 💰 Renewal | +| `sw_subscription_cancelled` | 😞 Cancelled Subscription | +| `sw_subscription_expired` | 😞 Expired Subscription | +| `sw_refund` | 🤬 Refunded Subscription | +| `sw_billing_issue` | 🫠 Billing Issue | +| `sw_product_change` | 😵‍💫 Product Change | +| `sw_non_renewing_purchase` | 💰 One-Time Purchase | + +## Testing the Integration + +### 1. Validate Credentials + +The integration validates your webhook URL by sending a test event. If the URL is invalid or the webhook has been deleted, validation will fail. + +### 2. Send a Test Event + +Trigger a subscription event from your app (or use sandbox mode) to verify messages appear correctly. + +### 3. Verify in Discord + +Check your configured channel for the notification: +- Confirm the embed appears with correct formatting +- Verify colors match the event type +- Check that fields display correct information + +### 4. Test Scenarios + +- [ ] New subscription shows green with 💰 emoji +- [ ] Trial start shows blue with 🤩 emoji +- [ ] Cancellation shows red with 😞 emoji +- [ ] Revenue field shows correct amount +- [ ] Sandbox events show "⚙️ Sandbox" field +- [ ] Revenue-only filter skips zero-price events +- [ ] Anonymous users show "Anonymous" or are skipped per setting +- [ ] Custom event names appear in title + +## Best Practices + +1. **Create a dedicated channel**: Keep subscription notifications separate from general chat to avoid noise and make monitoring easier. + +2. **Use Revenue Events Only for busy apps**: If you have high volume, filtering to revenue-only events reduces noise while keeping you informed of important transactions. + +3. **Set up channel notifications**: Configure Discord channel notification settings (e.g., only notify for @mentions) to avoid constant pings. + +4. **Consider multiple webhooks**: Create separate webhooks for different event types (e.g., one for revenue in `#sales`, one for all events in `#subscription-logs`). + +5. **Monitor billing issues**: Pay special attention to orange "🫠 Billing Issue" notifications—these represent potential revenue at risk. + +6. **Use meaningful custom names**: If you customize event names, make them clear and actionable for your team. + +## Common Use Cases + +### Sales Celebration Channel + +Create a `#sales` channel with revenue-only events: +```json +{ + "event_type": "Revenue Events Only", + "sales_reporting": "Revenue" +} +``` +Celebrate new subscribers and renewals with your team! + +### Churn Monitoring + +Create a `#churn-alerts` channel and filter to cancellation events using a separate integration instance: +- Monitor cancellation patterns +- Quickly identify if something is causing unusual churn +- React to billing issues before they become cancellations + +### Customer Success Integration + +Use the user ID field to quickly look up users in your CRM or support system: +- Click the dashboard URL in the embed to view user details +- Reach out proactively to users who cancelled +- Thank high-value subscribers personally + +### Team Revenue Dashboard + +Display the Discord channel on a team dashboard or TV: +- Real-time visualization of subscription activity +- Color-coded events make it easy to gauge health at a glance +- Celebrate wins and identify issues quickly + +## Troubleshooting + +### Messages Not Appearing + +**Possible causes:** +- Invalid webhook URL +- Webhook was deleted in Discord +- Channel permissions prevent webhook posting +- Event filtered out by `event_type` setting + +**Solutions:** +1. Verify the webhook still exists in Server Settings → Integrations +2. Check that the webhook has permission to post in the target channel +3. Confirm `event_type` setting includes the event you're expecting +4. Re-create the webhook if it was deleted + +### Webhook Rate Limited + +**Possible causes:** +- Discord rate limits webhook requests (30 requests per minute per channel) +- High volume of subscription events + +**Solutions:** +1. Use "Revenue Events Only" to reduce volume +2. Consider using a less busy channel +3. Events will be queued and retried automatically + +### Wrong Event Names or Emojis + +**Possible causes:** +- Custom `eventNameMappings` overriding defaults +- Unexpected event type mapping + +**Solutions:** +1. Review your `eventNameMappings` configuration +2. Check the event title reference table above +3. Remove custom mappings to restore defaults + +### Missing Revenue Field + +**Possible causes:** +- Event has zero price (normal for trial starts, cancellations) +- This is expected behavior + +**Solutions:** +- Revenue field only appears when price ≠ 0 +- Trial starts, cancellations, and expirations typically have no revenue + +### Sandbox Badge Appearing + +**Possible causes:** +- Event came from sandbox/test environment +- This is expected behavior + +**Solutions:** +- The "⚙️ Sandbox" field only appears for sandbox events +- Verify you're testing in the correct environment + +## Rate Limits + +Discord enforces rate limits on webhooks: + +| Limit | Value | +|-------|-------| +| Requests per minute | 30 per channel | +| Embed limit | 10 embeds per message | +| Total character limit | 6,000 characters per message | + +The integration sends one embed per event, which is well within these limits. For extremely high-volume applications, consider using the "Revenue Events Only" filter. + +## API Reference + +### Endpoint + +Events are sent directly to your Discord webhook URL: + +``` +POST https://discord.com/api/webhooks/{webhook_id}/{webhook_token} +``` + +### Request Headers + +``` +Content-Type: application/json +``` + +### Request Body + +```json +{ + "embeds": [ + { + "author": { + "name": "Superwall", + "icon_url": "https://superwall.com/favicon.ico" + }, + "title": "💰 New Subscriber", + "description": "$9.99 subscription started from United States", + "url": "https://superwall.com/applications/{app_id}", + "color": 3582031, + "thumbnail": { + "url": "https://superwall.com/favicon.ico" + }, + "fields": [ + { "name": "👤 User", "value": "user_123", "inline": true }, + { "name": "🎯 Product", "value": "com.app.premium", "inline": true }, + { "name": "📱 Store", "value": "App Store • United States", "inline": true }, + { "name": "💰 Revenue", "value": "$9.99", "inline": true } + ], + "timestamp": "2024-01-15T10:30:00.000Z", + "footer": { + "text": "Powered by Superwall", + "icon_url": "https://superwall.com/favicon.ico" + } + } + ] +} +``` + +### Response + +**Success**: `204 No Content` (Discord returns no body on success) + +**Error**: +- `400 Bad Request`: Invalid embed structure +- `401 Unauthorized`: Invalid webhook token +- `404 Not Found`: Webhook was deleted +- `429 Too Many Requests`: Rate limited diff --git a/content/docs/integrations/facebook-pixel.mdx b/content/docs/integrations/facebook-pixel.mdx new file mode 100644 index 0000000..5ed37df --- /dev/null +++ b/content/docs/integrations/facebook-pixel.mdx @@ -0,0 +1,516 @@ +--- +title: "Facebook Pixel" +description: "The Meta Conversion API integration sends subscription lifecycle events from Superwall directly to Facebook's server-side Conversion API. This enables accurate attribution for Facebook and Instagram ad campaigns, optimizes ad delivery for subscription events, and provides reliable tracking that isn't affected by browser privacy restrictions or ad blockers." +--- + +In the **Marketing** section within **Integrations**, you can connect your Facebook Pixel account to Superwall: + +![](../images/integrations-facebook-pixel.jpeg) + +## Features + +- **Server-Side Event Tracking**: Events are sent directly to Meta's servers, bypassing browser limitations +- **Standard Event Mapping**: Automatically maps subscription events to Meta's standard events (Subscribe, Purchase, StartTrial) +- **Sandbox Environment Support**: Separate Pixel ID and access token for testing without affecting production data +- **Test Event Mode**: Use test event codes to validate integration in Meta Events Manager +- **Flexible Revenue Reporting**: Report either gross revenue or net proceeds after store fees +- **Anonymous User Handling**: Configurable behavior for users without an identified app user ID +- **Custom Event Names**: Override default event mappings to match your existing Meta Pixel conventions +- **Deduplication**: Event IDs prevent duplicate events from being counted multiple times + +## Configuration + +### Required Settings + +| Field | Description | Example | +|-------|-------------|---------| +| `integration_id` | Must be set to `"meta"` | `"meta"` | +| `access_token` | Meta access token with `ads_management` permission | `"EAAG..."` | +| `pixel_id` | Your Facebook Pixel ID | `"123456789012345"` | +| `sales_reporting` | Whether to report gross Revenue or net Proceeds | `"Revenue"` or `"Proceeds"` | + +### Optional Settings + +| Field | Description | Default | +|-------|-------------|---------| +| `sandbox_access_token` | Separate access token for sandbox/test events | None (sandbox events skipped) | +| `sandbox_pixel_id` | Separate Pixel ID for sandbox/test events | None (sandbox events skipped) | +| `test_event_code` | Test event code for validation in Events Manager | None | +| `anonymous_user_behavior` | How to handle events from users without an app user ID | `"send"` | +| `eventNameMappings` | Custom mapping to rename default event names | None | + +### Example Configuration + +```json +{ + "integration_id": "meta", + "access_token": "EAAG1234567890abcdef...", + "pixel_id": "123456789012345", + "sales_reporting": "Revenue", + "sandbox_access_token": "EAAG0987654321fedcba...", + "sandbox_pixel_id": "543210987654321", + "test_event_code": "TEST12345", + "anonymous_user_behavior": "send", + "eventNameMappings": { + "sw_subscription_cancelled": "CancelSubscription", + "sw_refund": "Refund" + } +} +``` + +## Getting Your Credentials + +### Access Token + +1. Go to [Meta Events Manager](https://business.facebook.com/events_manager) +2. Select your Pixel from **Data Sources** +3. Click **Settings** tab +4. Scroll to **Conversions API** section +5. Click **Generate access token** or use an existing System User token +6. Copy the access token + +**Note**: The access token requires `ads_management` permission. For production use, Meta recommends using a System User token rather than a personal access token. + +### Pixel ID + +1. Go to [Meta Events Manager](https://business.facebook.com/events_manager) +2. Select your Pixel from **Data Sources** +3. The Pixel ID is displayed at the top of the page (e.g., "Pixel ID: 123456789012345") + +### Test Event Code (Optional) + +1. In Events Manager, select your Pixel +2. Click the **Test Events** tab +3. Your test event code is displayed (e.g., "TEST12345") +4. Events sent with this code appear in the Test Events tab for validation + +## Event Mapping + +Superwall events are mapped to Meta's standard events when possible. Using standard events enables Meta's machine learning to optimize ad delivery for specific conversion goals. + +### Standard Event Mappings + +| Superwall Event | Meta Standard Event | Description | +|-----------------|---------------------|-------------| +| `sw_subscription_start` | `Subscribe` | New paid subscription | +| `sw_trial_start` | `StartTrial` | Free trial begins | +| `sw_renewal` | `Purchase` | Subscription renewal payment | +| `sw_trial_converted` | `Purchase` | Trial converts to paid | +| `sw_intro_offer_converted` | `Purchase` | Intro offer converts to paid | + +### Custom Event Mappings + +Events without a standard Meta equivalent are sent with their Superwall event names: + +| Superwall Event | Meta Event Name | +|-----------------|-----------------| +| `sw_subscription_cancelled` | `sw_subscription_cancelled` | +| `sw_trial_cancelled` | `sw_trial_cancelled` | +| `sw_subscription_expired` | `sw_subscription_expired` | +| `sw_billing_issue` | `sw_billing_issue` | +| `sw_refund` | `sw_refund` | +| `sw_product_change` | `sw_product_change` | + +### Complete Event Mapping Reference + +| Superwall Event | Condition | Meta Event | +|-----------------|-----------|------------| +| `INITIAL_PURCHASE` | `periodType = Trial` | `StartTrial` | +| `INITIAL_PURCHASE` | `periodType = Normal` | `Subscribe` | +| `INITIAL_PURCHASE` | `periodType = Intro` | `sw_intro_offer_start` | +| `RENEWAL` | `periodType = Trial` | `Purchase` | +| `RENEWAL` | `periodType = Normal` | `Purchase` | +| `RENEWAL` | `isTrialConversion = true` | `Purchase` | +| `CANCELLATION` | `periodType = Trial` | `sw_trial_cancelled` | +| `CANCELLATION` | `periodType = Normal` | `sw_subscription_cancelled` | +| `EXPIRATION` | Any | `sw_*_expired` | +| Any event | `price < 0` | `sw_refund` | + +## Event Format + +Events are sent to Meta's Conversion API in the following format: + +### API Endpoint + +``` +POST https://graph.facebook.com/v21.0/{pixel_id}/events?access_token={access_token} +``` + +### Request Payload + +```json +{ + "data": [ + { + "event_name": "Subscribe", + "event_time": 1705312200, + "event_id": "evt_abc123", + "action_source": "app", + "user_data": { + "external_id": ["user_12345"] + }, + "custom_data": { + "value": 9.99, + "currency": "USD", + "content_type": "product", + "content_name": "com.app.premium.monthly", + "content_ids": ["com.app.premium.monthly"] + } + } + ], + "test_event_code": "TEST12345" +} +``` + +### Event Fields + +| Field | Description | Example | +|-------|-------------|---------| +| `event_name` | Meta standard event or custom event name | `"Subscribe"` | +| `event_time` | Unix timestamp in seconds | `1705312200` | +| `event_id` | Unique event ID for deduplication | `"evt_abc123"` | +| `action_source` | Always set to `"app"` for mobile app events | `"app"` | +| `user_data` | User identification data | `{"external_id": ["user_12345"]}` | +| `custom_data` | Event-specific data including revenue | See below | + +### Custom Data Fields + +| Field | Description | Example | +|-------|-------------|---------| +| `value` | Revenue amount (based on `sales_reporting` setting) | `9.99` | +| `currency` | ISO 4217 currency code | `"USD"` | +| `content_type` | Always `"product"` for subscription events | `"product"` | +| `content_name` | Product identifier | `"com.app.premium.monthly"` | +| `content_ids` | Array containing the product ID | `["com.app.premium.monthly"]` | + +## User Identification + +Meta's Conversion API requires user identification for event matching. The integration uses `external_id` to identify users. + +### Known Users + +For users with an `originalAppUserId` set in Superwall: + +```json +{ + "user_data": { + "external_id": ["user_12345"] + } +} +``` + +### Anonymous Users + +For users without an `originalAppUserId`, the behavior depends on `anonymous_user_behavior`: + +**When set to `"send"` (default)**: +- Events are sent with a synthetic ID: `$STORE_NAME:originalTransactionId` + +```json +{ + "user_data": { + "external_id": ["$APP_STORE:1000000123456789"] + } +} +``` + +**When set to `"dontSend"`**: +- Events from anonymous users are skipped entirely + +## Revenue Tracking + +### Revenue vs Proceeds + +The `sales_reporting` setting controls which amount is sent in the `value` field: + +- **Revenue**: The full price charged to the customer (e.g., $9.99) +- **Proceeds**: The amount after store fees are deducted (e.g., $8.49 after Apple's 15-30% commission) + +### Zero-Value Events + +For events without revenue (cancellations, expirations), the `value` and `currency` fields are omitted: + +```json +{ + "custom_data": { + "content_type": "product", + "content_name": "com.app.premium.monthly", + "content_ids": ["com.app.premium.monthly"] + } +} +``` + +### Refund Events + +Refunds are sent with negative values: + +```json +{ + "event_name": "sw_refund", + "custom_data": { + "value": -9.99, + "currency": "USD", + "content_type": "product", + "content_name": "com.app.premium.monthly", + "content_ids": ["com.app.premium.monthly"] + } +} +``` + +## Sandbox Handling + +The integration supports separate handling for sandbox (test) events: + +### With Sandbox Credentials Configured + +When both `sandbox_pixel_id` and `sandbox_access_token` are provided: +- Production events use the main credentials +- Sandbox events use the sandbox credentials +- Events are tracked separately in Meta Events Manager + +### Without Sandbox Credentials + +When sandbox credentials are not provided: +- Production events are sent normally +- Sandbox events are **skipped entirely** +- This prevents test data from affecting your production Pixel + +## Test Event Mode + +Use the `test_event_code` setting to validate your integration without affecting production data: + +1. Get your test event code from Meta Events Manager → Test Events +2. Add `test_event_code` to your configuration +3. Send test events from your app +4. View events in the Test Events tab of Events Manager + +Events sent with a test event code: +- Appear in the Test Events tab +- Are **not** counted in your main event metrics +- Are **not** used for ad optimization +- Help validate your integration before going live + +**Important**: Remove the `test_event_code` before deploying to production. + +## Custom Event Names + +Use `eventNameMappings` to override default event names: + +```json +{ + "eventNameMappings": { + "sw_trial_start": "CustomTrialStart", + "sw_subscription_start": "CustomSubscribe", + "sw_renewal": "CustomRenewal" + } +} +``` + +**Note**: Mappings override both standard and custom event names. For example, mapping `sw_subscription_start` will send your custom name instead of the Meta standard `Subscribe` event. + +## Testing the Integration + +### 1. Configure Test Event Code + +Add your `test_event_code` from Meta Events Manager to validate events without affecting production metrics. + +### 2. Send Test Events + +Trigger subscription events from your app in sandbox mode. + +### 3. Verify in Meta Events Manager + +1. Go to Meta Events Manager → your Pixel +2. Click the **Test Events** tab +3. Look for events with your test event code +4. Verify event names, parameters, and user data are correct + +### 4. Check Event Quality + +1. Go to Meta Events Manager → your Pixel → **Overview** +2. Check the **Event Match Quality** score +3. Higher scores indicate better event matching + +### 5. Test Scenarios + +Verify these scenarios work correctly: + +- [ ] Production event sends to main Pixel +- [ ] Sandbox event sends to sandbox Pixel (if configured) +- [ ] Sandbox event is skipped when no sandbox credentials +- [ ] Trial start maps to `StartTrial` +- [ ] Subscription start maps to `Subscribe` +- [ ] Renewal maps to `Purchase` +- [ ] Cancellation sends as custom event +- [ ] Anonymous users handled per configuration +- [ ] Revenue is included for paid events +- [ ] Test event code appears in Test Events tab + +## Best Practices + +1. **Use System User tokens**: For production, create a System User in Meta Business Manager and use its access token instead of a personal token for better security and stability. + +2. **Configure sandbox credentials**: Use a separate test Pixel for development to keep your production data clean. + +3. **Remove test event code for production**: Test event codes prevent events from being used for optimization. + +4. **Match user IDs across platforms**: Use consistent `external_id` values between your Pixel browser events and server events for better cross-device attribution. + +5. **Monitor Event Match Quality**: Check your Event Match Quality score in Events Manager regularly. Scores below 6.0 indicate potential matching issues. + +6. **Use standard events when possible**: Standard events like `Subscribe`, `Purchase`, and `StartTrial` enable Meta's machine learning to optimize for those specific conversions. + +## Common Use Cases + +### Optimizing Campaigns for Subscriptions + +1. Send `Subscribe` events for new paid subscriptions +2. Create a Custom Conversion in Meta Ads Manager based on `Subscribe` +3. Optimize your campaigns for subscription conversions +4. Meta will show your ads to users most likely to subscribe + +### Measuring Trial-to-Paid Conversion + +1. Track `StartTrial` events for trial starts +2. Track `Purchase` events for trial conversions +3. Create a funnel in Meta Analytics +4. Analyze conversion rate and time-to-convert + +### Retargeting Churned Users + +1. Track `sw_subscription_cancelled` events +2. Create a Custom Audience of users who cancelled +3. Run re-engagement campaigns with special offers +4. Exclude recent subscribers to avoid wasted ad spend + +### Value-Based Optimization + +1. Include revenue in `custom_data.value` +2. Create Value-Based Custom Conversions +3. Optimize campaigns for highest value subscribers +4. Meta prioritizes showing ads to users likely to generate more revenue + +## Troubleshooting + +### Events Not Appearing in Events Manager + +**Possible causes:** +- Invalid access token (expired or insufficient permissions) +- Incorrect Pixel ID +- Sandbox events without sandbox credentials (events are skipped) +- Test event code routing events to Test Events tab only + +**Solutions:** +1. Verify your access token has `ads_management` permission +2. Confirm your Pixel ID matches Events Manager +3. Check for sandbox credentials if testing +4. Remove `test_event_code` to see events in main Overview + +### Authentication Errors (Error 190) + +**Possible causes:** +- Access token has expired +- Token doesn't have required permissions +- Token was revoked + +**Solutions:** +1. Generate a new access token in Events Manager +2. Ensure the token has `ads_management` permission +3. Consider using a System User token for stability + +### Low Event Match Quality + +**Possible causes:** +- Only `external_id` is being sent +- No additional user data available + +**Solutions:** +- Event Match Quality can be improved by including additional user data fields (email, phone, IP address) if available in your webhook data +- Ensure `external_id` values are consistent with other data sources + +### Events Show as "Duplicate" + +**Possible causes:** +- Same event being sent multiple times +- Event ID collision + +**Solutions:** +- The integration uses the Superwall event ID as `event_id` for deduplication +- Verify your webhook isn't triggering multiple times for the same event + +### Wrong Event Names + +**Possible causes:** +- Custom event name mappings overriding standard events +- Unexpected event type mapping + +**Solutions:** +- Review your `eventNameMappings` configuration +- Check the event mapping reference table above +- Test with `test_event_code` to verify event names + +## Rate Limits + +Meta's Conversion API has the following limits: + +| Limit | Value | +|-------|-------| +| Requests per hour | 10,000 per Pixel | +| Events per request | 1,000 maximum | +| Request body size | 1MB maximum | + +The integration sends one event per webhook, which is well within these limits. For high-volume applications, Meta automatically handles queuing. + +## API Reference + +### Endpoint + +``` +POST https://graph.facebook.com/v21.0/{pixel_id}/events +``` + +### Authentication + +Access token passed as URL parameter: + +``` +?access_token={access_token} +``` + +### Request Headers + +``` +Content-Type: application/json +Accept: */* +``` + +### Response + +**Success (200 OK)**: +```json +{ + "events_received": 1, + "messages": [], + "fbtrace_id": "ABC123..." +} +``` + +**Error (400/401/403)**: +```json +{ + "error": { + "message": "Invalid OAuth access token.", + "type": "OAuthException", + "code": 190, + "fbtrace_id": "ABC123..." + } +} +``` + +## Additional Resources + +- [Meta Conversion API Documentation](https://developers.facebook.com/docs/marketing-api/conversions-api) +- [Server Events Parameters Reference](https://developers.facebook.com/docs/marketing-api/conversions-api/parameters) +- [Event Quality Scoring Guide](https://www.facebook.com/business/help/765081237991954) +- [App Events Best Practices](https://developers.facebook.com/docs/app-events/best-practices) +- [Meta Events Manager](https://business.facebook.com/events_manager) diff --git a/content/docs/integrations/meta.json b/content/docs/integrations/meta.json index 2c69a54..9f20529 100644 --- a/content/docs/integrations/meta.json +++ b/content/docs/integrations/meta.json @@ -7,11 +7,16 @@ "---Integrations---", "webhooks", "apple-search-ads", + "facebook-pixel", "mixpanel", + "adjust", "amplitude", + "posthog", + "customer-io", "firebase", "statsig", "slack", + "discord", "figma-plugin" ] } \ No newline at end of file