Appearance
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/charactersAuthenticate 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_type | What 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. |
refund | Reverses a prior purchase — decrements sale_count, subtracts from total_spend, and may demote the lifecycle stage. |
chargeback | Like 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
/chator 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
| Name | Type | Required | Description |
|---|---|---|---|
platform | string | Yes | The platform the lead is on, e.g. "instagram". Selects the character's bound workflow. |
handle | string | Yes | The lead's @username on that platform. (platform, handle) identifies the lead. |
event_type | string | No | One of "purchase", "refund", "chargeback". Defaults to "purchase". Any other value → 422. |
external_event_id | string | Yes | Your idempotency key for this event (unique per owner + platform). A re-delivered event with the same key is applied exactly once. See Idempotency. |
amount | number | No | The transaction amount. Added to (or subtracted from) the lead's total_spend. |
currency | string | No | ISO currency code. Defaults to "USD". |
raw | object | No | An 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:
| Field | Type | Description |
|---|---|---|
ok | boolean | Always true on a 2xx. |
deduped | boolean | true if this external_event_id had already been applied — the event was not re-applied. |
event_type | string | The event that was applied: purchase, refund, or chargeback. |
stage | string | The lead's lifecycle stage after the event (e.g. customer after a first purchase). |
sale_count | integer | The lead's total number of recorded purchases after applying. |
total_spend | number | The 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
| Status | error.code | When |
|---|---|---|
400 | invalid_request | Malformed request (e.g. the body isn't valid JSON). |
401 | invalid_token | Missing, invalid, or revoked connector token. |
403 | forbidden | The token's character isn't bound to this platform, or the platform isn't enabled for it. |
422 | validation_error | A required field is missing (platform, handle, external_event_id) or event_type is not one of purchase/refund/chargeback. |
429 | rate_limited | Too many requests — retry after the Retry-After header. |
500 | internal_error | Unexpected 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.
Related
- 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