> ## Documentation Index
> Fetch the complete documentation index at: https://docs.greenflash.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# Segment

> Forward every Segment track event into Greenflash through a single Webhook destination — zero app changes, your existing instrumentation does the work.

If you already use Segment as your event pipeline, this is the lowest-effort way to put outcome data behind your AI conversations. Every `analytics.track(...)` call you've already written keeps flowing to your existing destinations *and* reaches Greenflash, where it's mapped into a business event and used to tell which conversation patterns lead to real outcomes.

## How it works

Segment has a built-in **Webhooks** destination that POSTs each message to one or more URLs. Greenflash gives you a per-product URL to add as one of those URLs. The flow:

1. Your app calls `analytics.track('Order Completed', { revenue: 49 })` (or any other event).
2. Segment routes the message to its configured destinations, including Greenflash.
3. Greenflash authenticates the URL token, maps the Segment message to a Greenflash business event, infers positive / negative / neutral influence, and attaches it to the product you connected.
4. The next conversation analysis Greenflash runs has those events as outcome context.

Your Segment instrumentation stays the source of truth. Greenflash sees a copy of the messages your existing destinations already see.

## Before you start

* **A Greenflash product** to enrich with Segment events. Create one under **Settings → Products** if you don't have one.
* **Owner / source admin access in Segment** so you can add a destination to a source.
* (Optional) Segment Functions or a Cloud Mode destination if you want to batch messages before forwarding.

## Setup

### 1. Create the connection in Greenflash

1. Open **Settings → Connect → Events** in the Greenflash web app.
2. Click **Connect** on the Segment card.
3. Pick the product the stream should feed and click **Create source**.
4. Copy the destination URL. Greenflash shows it once. If you lose it, rotate the token from the connection row and update Segment.

<Tip>
  The URL is the secret. Store it in Segment and your secrets manager — anyone who has it can write events into the connected product.
</Tip>

### 2. Add the Webhook destination in Segment

1. In Segment, open the source you want to forward from and go to **Destinations → Add Destination**.
2. Search for **Webhooks** (the built-in `webhook` destination) and add it.
3. In the destination settings, paste the Greenflash URL into one of the **Webhook URL** slots. Leave the method as `POST`.
4. Under the destination's event settings, configure which message types forward. **We recommend forwarding only `track` events** — Greenflash drops `identify`, `group`, and `alias` server-side because they describe user traits rather than business actions, so filtering them at the Segment side saves request volume. `page` and `screen` events are accepted if you want them.
5. Enable the destination. The connection row in Greenflash flips to **Active** on the first event.

<Note>
  **Multiple sources?** Add the same Webhook URL to every source whose events you want to forward. Or set up one Greenflash connection per product, each with its own URL, and wire them to different Segment sources.
</Note>

### 3. Send a test event

Fire a `track` event from your app, replay one from the Segment Source debugger, or post directly to the URL to confirm the round trip:

```bash theme={"theme":{"light":"github-light","dark":"vesper"}}
curl -X POST "<YOUR_GREENFLASH_URL>" \
  -H "Content-Type: application/json" \
  -d '{
    "type": "track",
    "event": "Order Completed",
    "userId": "user_test_123",
    "messageId": "msg_test_001",
    "timestamp": "2026-05-18T12:00:00Z",
    "properties": {
      "revenue": 49.0,
      "plan": "pro"
    }
  }'
```

A successful request returns:

```json theme={"theme":{"light":"github-light","dark":"vesper"}}
{ "received": 1, "accepted": 1, "dropped": 0 }
```

If `received` is `0`, the payload didn't match the Segment message shape — check your Segment Function output or webhook template.

## How events are mapped

Greenflash applies these defaults to every Segment `track` message:

| Segment field                                                      | Greenflash field                                        |
| ------------------------------------------------------------------ | ------------------------------------------------------- |
| `event`                                                            | `eventType`                                             |
| `userId` (falls back to `anonymousId`)                             | `externalUserId`                                        |
| `timestamp`                                                        | `eventAt`                                               |
| `messageId`                                                        | `insertId` (prefixed `segment:`) — used for idempotency |
| `properties.revenue` (or `properties.price * properties.quantity`) | `value` (with `valueType: currency`)                    |
| `properties` (entire object)                                       | `properties` (preserved verbatim)                       |

### Identity hints (used for user & organization attribution)

Greenflash reads a handful of fields from `context.traits` / `traits` / `properties` to attach the event to the right user and organization. Sending these means Greenflash can auto-create the user / org if it hasn't seen them yet.

| Segment field                                                                    | Greenflash field         |
| -------------------------------------------------------------------------------- | ------------------------ |
| `context.traits.email` (or `traits.email`, or `properties.email`)                | `userHints.email`        |
| `context.traits.name` (or `traits.name`, or `properties.name`)                   | `userHints.name`         |
| `context.groupId` (or `traits.organization_id`, or `properties.organization_id`) | `externalOrganizationId` |
| `traits.organization_name` / `traits.company` (or the same under `properties`)   | `organizationHints.name` |

## Attribution: how events tie to users, orgs, and conversations

**Users.** Events match an existing Greenflash user by `externalUserId` (= Segment `userId`, falling back to `anonymousId`) first, then by `email`. If no match exists *and* the message carries an `email`, Greenflash **upserts** a new user automatically. Anonymous events (no email) keep `userId = null` rather than creating a row per anon ID.

**Organizations.** Same idea, scoped to `externalOrganizationId`. Org upsert only runs when `context.groupId` (or equivalent) is present — never by name.

**Conversations.** Add `gf_conversation_id` to `properties` to attach the event to a specific Greenflash conversation. Otherwise, the event auto-attaches to the user's most-recent conversation in this product within the last **15 minutes**; if that doesn't match, `conversationId` stays `null`.

> Users and orgs auto-created from analytics carry `properties.created_from = 'analytics'` and `properties.source = 'segment'`. When you later identify them via the Greenflash SDK using the same `externalUserId`, the existing row is reused.

### Other Segment message types

* **`page` / `screen`** — accepted. The `name` field becomes the Greenflash event name; if no name is set, the type itself (`'page'` or `'screen'`) is used.
* **`identify`, `group`, `alias`** — silently dropped. These describe user properties, not behaviors. Move the trait change into a `track` call if you want it in Greenflash.

### Influence (positive / negative / neutral)

* **Positive** when `revenue > 0`, *or* the event name matches `signup`, `signed_up`, `upgrade`, `purchase`, `subscribe`, `convert`, `activate`, `complete`, or `order_completed`.
* **Negative** when `revenue < 0`, *or* the event name matches `churn`, `cancel`, `unsubscribe`, `refund`, `downgrade`, `delete`, `failed`, or `error`.
* **Neutral** otherwise.

Matching is case-insensitive and works across `snake_case`, `kebab-case`, `PascalCase`, and the `Title Case` Segment conventionally uses (`Order Completed`, `Signed Up`).

### Batched payloads

Segment Functions and some Cloud Mode destinations send `{ batch: [{ ... }, { ... }] }` instead of one message per request. Greenflash accepts both shapes and ingests one event per message in the batch.

## Override the defaults

Add any of these *magic properties* inside the event's `properties` and Greenflash will respect them — no Greenflash-side configuration needed.

| Property                  | Effect                                                                               |
| ------------------------- | ------------------------------------------------------------------------------------ |
| `gf_event_type`           | Override the Greenflash event name (the original Segment name stays in `properties`) |
| `gf_influence`            | Force `positive`, `negative`, or `neutral`                                           |
| `gf_quality_impact_score` | Numeric override from `-1.0` to `1.0` — fine-grained weight                          |
| `gf_value`                | Override the event value (string or number)                                          |
| `gf_value_type`           | One of `currency`, `numeric`, `text`, `boolean`                                      |
| `gf_conversation_id`      | Attach this event to a specific Greenflash conversation (`externalConversationId`)   |

Example:

```js theme={"theme":{"light":"github-light","dark":"vesper"}}
analytics.track('Workspace Activated', {
  workspace_id: 'ws_42',
  gf_influence: 'positive',
  gf_quality_impact_score: 0.7,
});
```

## Filtering: what to forward

**Filter on the Segment side, not inside Greenflash.** Segment was built around filtering at the source — destination event filters, source schema controls, Destination Functions — all of which are more expressive than anything we'd build into Greenflash. We deliberately don't expose a denylist UI: the only Greenflash-side knob is the magic-property overrides above, applied per event in `properties`.

Some practical choices:

* **Destination event filters** (built-in, free tier) — allow specific `track` event names, drop the rest. Set once, no code.
* **Source schema controls** (Business plan) — block events at the source so they never reach *any* destination.
* **Destination Functions** — write a small JS function that decides per message whether to forward, or rewrites payloads (great for adding `gf_*` overrides).

<AccordionGroup>
  <Accordion title="SaaS conversion" icon="dollar-sign">
    Forward `Signed Up`, `Trial Started`, `Activated`, `Subscription Started`, `Subscription Upgraded`, `Subscription Cancelled`. Greenflash infers influence from these names automatically.
  </Accordion>

  <Accordion title="E-commerce" icon="shopping-cart">
    Forward `Order Completed`, `Order Refunded`, `Cart Abandoned`. Include `revenue` or both `price` and `quantity` so Greenflash can value-weight outcomes. Refunds should arrive with negative `revenue`.
  </Accordion>

  <Accordion title="Hard-mute a stream temporarily" icon="circle-pause">
    Don't want to disable the Segment destination for a short outage or experiment? Pause the connection from its row in **Settings → Connect → Events**. The Greenflash receiver returns `204` while paused so Segment stops retrying. Re-enable to resume — no token rotation, no Segment change.
  </Accordion>
</AccordionGroup>

## Response codes & limits

| Code  | Meaning                                                                                                         |
| ----- | --------------------------------------------------------------------------------------------------------------- |
| `200` | Payload received and processed. Body includes `received`, `accepted`, `dropped`.                                |
| `204` | Connection is paused or disconnected in Greenflash. Returned deliberately so Segment stops retrying.            |
| `400` | Payload didn't match the Segment message schema, or JSON was invalid.                                           |
| `401` | URL token is invalid, the connection has been rotated, or the optional shared secret header is missing / wrong. |
| `404` | Provider segment of the URL is wrong (e.g. you pointed Segment at the `/posthog/` URL).                         |
| `413` | More than **1,000 messages** in a single batch. Split the batch on the Segment side.                            |
| `500` | Greenflash couldn't look up the connection. Safe to retry.                                                      |

## Security

The URL is path-authenticated: the token segment is generated per connection, stored only as a `scrypt` hash, and verified in constant time on every request.

Greenflash does **not** verify Segment's native `X-Webhook-Signature` HMAC. The destination's signing key would have to be configured per-connection, and the URL token already provides a stronger boundary.

For defense in depth, you can configure a **shared secret** alongside the URL token. The secret is communicated to Greenflash on every request via a custom HTTP header — there's no body field or query parameter.

1. In Segment, open your Webhook destination and go to **Headers**.
2. Add a header with name `x-greenflash-secret` and value `<your-secret>` (any string you pick, 8+ characters).
3. Provide the same secret to Greenflash on the connection so we can match it.

Once configured, Greenflash rejects any request whose `x-greenflash-secret` header is missing or doesn't match with `401`.

**Rotating the URL** invalidates the previous token immediately. Events sent to the old URL return `401`. Update the URL on the Segment Webhook destination to resume delivery.

## Troubleshooting

<AccordionGroup>
  <Accordion title="Connection still says ''Awaiting first event''" icon="hourglass">
    * The destination is enabled and the source is firing — check the Segment **Source debugger** for live events.
    * The URL has **no trailing slash**.
    * You pasted the URL into the right Webhook URL slot (the destination supports up to five).
    * Your destination's event filter isn't excluding everything.
  </Accordion>

  <Accordion title="`identify` / `group` events not appearing" icon="info">
    That's intentional. Greenflash only ingests `track`, `page`, and `screen` — trait updates are dropped. Move the trait change into a `track` call (e.g. `analytics.track('Profile Updated', { plan: 'pro' })`) if you want it in Greenflash.
  </Accordion>

  <Accordion title="Getting 413 errors from batched Functions" icon="package">
    The batch contained more than 1,000 messages. Chunk the batch on the Segment Function side — Greenflash drops the entire batch above the cap rather than partially ingesting it.
  </Accordion>

  <Accordion title="Lost the URL" icon="key">
    Open the Segment row under **Settings → Connect → Events** and click **Rotate URL**. The old token is invalidated immediately. Paste the new URL into the Segment Webhook destination.
  </Accordion>

  <Accordion title="Duplicate events" icon="copy">
    Greenflash deduplicates on the `segment:<messageId>` insert id. As long as Segment sends a stable `messageId` per message, retries won't create duplicates.
  </Accordion>
</AccordionGroup>

## Next

<CardGroup cols={2}>
  <Card title="How Greenflash uses events" icon="activity" href="/features/events">
    What changes in your analyses once events start flowing.
  </Card>

  <Card title="Public Events API" icon="bolt" href="/features/public-api">
    Skip the webhook and post events directly if you want full control.
  </Card>
</CardGroup>
