Skip to content

API Reference — Events

Report that money moved for a lead — a purchase, a refund, or a chargeback. The connector reports the fact; FluidTalk authoritatively updates the lead's lifecycle and spend, and the character adapts its tone on the next turn (post-sale aftercare, retention, a re-sell, and so on). This endpoint does not generate or send a message — it only records what happened.

All routes are under the production base URL:

https://api-talk.fluidvip.com/api/v1/characters

Authenticate with your per-character connector token in the X-Connector-Token header — the token is the character, and the platform in the body selects the bound workflow. See Authentication. Every response is wrapped in the standard envelope ({ "data": …, "request_id": "req_…" }, plus an X-Request-Id header); the shapes below show the inner data. See conventions.

The connector reports, the character decides. You don't tell FluidTalk what stage the lead should be in — you tell it that a purchase (or refund, or chargeback) occurred, and the API flips the lead's lifecycle authoritatively. A first purchase promotes the lead toward a customer stage, raises warmth to the post-sale floor, and increments their sale count and lifetime spend; a refund or chargeback reverses that. Your job is just to deliver the event exactly once.


How the lifecycle flip works

A lead is one consistent Person across DMs and comments. Recording a money event updates that Person's lifecycle state, which is what the character reads on its next /chat turn or trigger — so the same persona that pitched the lead now talks to them as a paying customer.

event_typeWhat it does to the lead
purchase (default)Records a sale. Promotes the lifecycle (first sale → customer; later sales → re-sell / retention), raises warmth to the post-sale floor, increments sale_count, and adds amount to total_spend.
refundReverses a prior purchase — decrements sale_count, subtracts from total_spend, and may demote the lifecycle stage.
chargebackLike a refund, applied as a forced reversal of the lead's purchase state.

No message is generated. Recording an event never produces a reply and is not metered — it only updates the lead's record. The character's tone change shows up the next time you call /chat or fire a /trigger. For how generated turns are billed, see Billing.


POST /events

Report a purchase, refund, or chargeback for a lead identified by (platform, handle).

Request body

NameTypeRequiredDescription
platformstringYesThe platform the lead is on, e.g. "instagram". Selects the character's bound workflow.
handlestringYesThe lead's @username on that platform. (platform, handle) identifies the lead.
event_typestringNoOne of "purchase", "refund", "chargeback". Defaults to "purchase". Any other value → 422.
external_event_idstringYesYour idempotency key for this event (unique per owner + platform). A re-delivered event with the same key is applied exactly once. See Idempotency.
amountnumberNoThe transaction amount. Added to (or subtracted from) the lead's total_spend.
currencystringNoISO currency code. Defaults to "USD".
rawobjectNoAn optional echo of your platform's event payload, stored for your reference. Any token-like values are stripped before storage.

Response

The full envelope on a first application:

json
{
  "data": {
    "ok": true,
    "deduped": false,
    "event_type": "purchase",
    "stage": "customer",
    "sale_count": 1,
    "total_spend": 24.0
  },
  "request_id": "req_8f3c1a2b4d5e6f70"
}

The inner data fields:

FieldTypeDescription
okbooleanAlways true on a 2xx.
dedupedbooleantrue if this external_event_id had already been applied — the event was not re-applied.
event_typestringThe event that was applied: purchase, refund, or chargeback.
stagestringThe lead's lifecycle stage after the event (e.g. customer after a first purchase).
sale_countintegerThe lead's total number of recorded purchases after applying.
total_spendnumberThe lead's lifetime spend (in the reporting currency) after applying.

Idempotency

/events is idempotent on external_event_id. Webhooks get re-delivered, networks drop — so if the same event arrives twice, the second call is recognized and the lifecycle is not changed again. A duplicate returns a minimal body with deduped: true and omits the recomputed fields:

json
{
  "data": { "ok": true, "deduped": true },
  "request_id": "req_9a0b1c2d3e4f5061"
}

Use a stable, unique id from your payment provider (for example the Stripe payment-intent or charge id). Retrying a dropped /events call with the same external_event_id is always safe.

Examples

bash
curl -X POST https://api-talk.fluidvip.com/api/v1/characters/events \
  -H "X-Connector-Token: ftc_live_8f3c..." \
  -H "Content-Type: application/json" \
  -d '{
    "platform": "instagram",
    "handle": "mark",
    "event_type": "purchase",
    "external_event_id": "stripe_pi_3Q1aZ2",
    "amount": 24.00,
    "currency": "USD"
  }'
python
from fluidtalk import FluidTalk

ft = FluidTalk(token="ftc_live_8f3c...")

result = ft.event(
    platform="instagram",
    handle="mark",
    event_type="purchase",
    external_event_id="stripe_pi_3Q1aZ2",
    amount=24.00,
    currency="USD",
)
print(result.stage, result.sale_count, result.total_spend)

# A refund reverses the lead's purchase state.
ft.event(
    platform="instagram",
    handle="mark",
    event_type="refund",
    external_event_id="stripe_re_8Kp0Lm",
    amount=24.00,
)
typescript
import { FluidTalk } from "fluidtalk";

const ft = new FluidTalk({ token: "ftc_live_8f3c..." });

const result = await ft.event({
  platform: "instagram",
  handle: "mark",
  eventType: "purchase",
  externalEventId: "stripe_pi_3Q1aZ2",
  amount: 24.0,
  currency: "USD",
});
console.log(result.stage, result.sale_count, result.total_spend);

// A refund reverses the lead's purchase state.
await ft.event({
  platform: "instagram",
  handle: "mark",
  eventType: "refund",
  externalEventId: "stripe_re_8Kp0Lm",
  amount: 24.0,
});

Errors

Statuserror.codeWhen
400invalid_requestMalformed request (e.g. the body isn't valid JSON).
401invalid_tokenMissing, invalid, or revoked connector token.
403forbiddenThe token's character isn't bound to this platform, or the platform isn't enabled for it.
422validation_errorA required field is missing (platform, handle, external_event_id) or event_type is not one of purchase/refund/chargeback.
429rate_limitedToo many requests — retry after the Retry-After header.
500internal_errorUnexpected server error. Safe to retry with the same external_event_id.

A duplicate external_event_id is not an error — it returns 200 with deduped: true (see Idempotency). The SDKs raise typed errors (AuthError, PaymentRequiredError, PermissionError, NotFoundError, ConflictError, ValidationError, RateLimitError, ApiError) — see Python and TypeScript. The full error model is on the Errors page.


  • Chat — push an inbound DM and get the character's reply (where the post-sale tone change shows up).
  • Triggers — fire entry events (story reaction, new follower, custom); also idempotent on external_event_id.
  • Follow-ups — pull proactive re-engagement messages for dormant leads.
  • Core concepts — Person, lifecycle, and cross-surface awareness.
  • Billing — how generated turns are metered (events themselves are not).
  • API Reference index

FluidTalk Characters API — part of the Fluidvip ecosystem.