Customer Usage Events
Pilot — Customer usage events are part of the customer risk pilot. Start there for what the feature does and how to join.
If you sell usage (API calls, tokens, minutes), send Inflow one event each time one of your customers consumes something. Inflow reviews the activity of your customers and tells you, with the risk.customer_flagged webhook, when a customer looks risky and what to do about it.
Usage events are for risk detection only. They are not invoiced and do not change any amount. The API only receives events: there is no route to read them back.
Authenticate with a secret API key (X-Inflow-Api-Key). Public and SDK keys are not accepted.
How it works
- Your server sends usage events, one by one or in batches of up to 100.
- Inflow checks each event at once and answers
accepted,duplicateor an error. An accepted event is final: nothing about it is reported later. - Inflow analyses the activity of your customers twice a day. A person at Inflow reviews every finding before you hear about it.
- When a finding is confirmed, you receive
risk.customer_flaggedwith a level and a recommended action. You decide what to do.
You never receive a score or the rule that fired.
Send one event
POST https://api.inflowpay.com/api/customer-usage-events
X-Inflow-Api-Key: inflow_prod_xxx
Content-Type: application/json
{
"identifier": "req_01HZX3",
"customer_id": "cus_abc123",
"occurred_at": 1792504992,
"resource": "llm_tokens",
"quantity": 1550,
"client_ip": "203.0.113.7",
"client_user_agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7)",
"client_country": "FR",
"api_key_id": "key_42"
}Answer (200):
{ "status": "accepted", "id": "cue_…", "identifier": "req_01HZX3" }Fields
| Field | Required | Rules |
|---|---|---|
identifier | Yes | Your own id for the event. 1 to 255 characters among A-Z a-z 0-9 _ - : .. Unique per account, for good. |
customer_id | Yes | The Inflow customer who consumed (cus_…). It must be one of your customers. |
occurred_at | Yes | When the consumption happened, as a Unix timestamp in seconds. At most 35 days in the past, 5 minutes in the future. |
resource | Yes | What was consumed, in your words (a model, an endpoint). 1 to 64 characters among a-z 0-9 _ . -. |
quantity | Yes | How much was consumed, in the unit of the resource (tokens, calls, minutes). An integer from 0 to 9007199254740991. |
client_ip | No | The IPv4 or IPv6 address of your end user, as you saw it. No leading zeros in an IPv4 octet, no zone id (%eth0). |
client_user_agent | No | The User-Agent header of your end user. Characters other than printable ASCII are removed; the first 512 are kept. |
client_country | No | The country of your end user, as an ISO 3166-1 alpha-2 code (FR). Inflow checks two letters and stores them in upper case. |
api_key_id | No | The id of the key you gave your end user, never the key itself. 1 to 128 visible ASCII characters. |
Any other field is refused. null on an optional field means "not sent".
Compare quantities within one resource only: Inflow never adds tokens to minutes.
Send a batch
Send up to 100 events in one call. A batch body can be up to 512 kB; a single event, up to 100 kB. A larger body gets a 413 and nothing is written.
POST https://api.inflowpay.com/api/customer-usage-events/batch
X-Inflow-Api-Key: inflow_prod_xxx
Content-Type: application/json
{
"events": [
{ "identifier": "req_1", "customer_id": "cus_abc123", "occurred_at": 1792504992, "resource": "llm_tokens", "quantity": 1550 },
{ "identifier": "req_2", "customer_id": "cus_unknown", "occurred_at": 1792504993, "resource": "llm_tokens", "quantity": 20 }
]
}Answer (200), one result per event, in the order sent:
{
"results": [
{ "index": 0, "status": "accepted", "id": "cue_…", "identifier": "req_1" },
{
"index": 1,
"status": "rejected",
"error": { "code": "customer_not_found", "field": "customer_id", "message": "no customer with this id for this merchant" }
}
]
}A rejected event does not fail the others. Two valid events with the same identifier in one batch: the first is accepted, the second is duplicate. An invalid event is rejected, whatever its identifier.
A body that is not { "events": [ … ] } with 1 to 100 events, or that has another field, gets a 400 and nothing is written.
Retries and duplicates
The identifier makes every call safe to retry. An identifier already accepted answers duplicate, with the id of the first event, and nothing is written again. This is true even when the event would now be refused (for example, it is older than 35 days).
Inflow does not compare the content: an identifier already accepted answers duplicate even when the other fields differ, and the new values are not kept. Use a new identifier for each new consumption.
A rejected event reserves nothing: fix it and send it again with the same identifier.
On a timeout, a 429 or a 5xx, send the same events again. The 429 of the per-account limit carries a Retry-After header, in seconds.
Errors
A rejected single event gets a 400 with an error body. code names the error, and field names the field:
{
"statusCode": 400,
"message": "must be an IPv4 address (four octets) or an IPv6 address",
"code": "invalid_client_ip",
"field": "client_ip"
}In a batch, the same code, field and message are in results[].error. A body that is not valid JSON gets a 400 without a code. While risk analysis is not available, both endpoints answer 503: send the events again later.
code | Meaning |
|---|---|
invalid_request | A field is missing, has the wrong type or format, or is unknown. |
invalid_client_ip | client_ip is not an IPv4 or IPv6 address. |
customer_not_found | No customer with this id on your account (another account's customer too). |
occurred_at_out_of_range | occurred_at is more than 35 days in the past or 5 minutes in the future. |
Marketplaces
A marketplace sends the events of a sub-merchant's customers with the X-On-Behalf-Of header, like any other merchant call. See Acting on behalf of. Without the header, the customer must belong to the marketplace itself. Identifiers are unique per sub-merchant.
Rate limit
Usage events have their own limit: 50 requests per second per account. They do not use the budget of your other API calls, and your other calls do not slow them down. A marketplace shares this limit with the calls it makes on behalf of its sub-merchants. To send more, use batches. See Rate limits.
Data retention
client_ip and client_user_agent are deleted 12 months after Inflow receives the event. The rest of the event is kept as the record of the usage.
Receive the recommendations
Subscribe your webhook endpoint to risk.customer_flagged. A marketplace receives risk.customer_flagged for its own customers and connect.risk.customer_flagged for the customers of its sub-merchants: subscribe to both. See Customer risk for the payload.
Inflow analyses the last 24 hours and the last 7 days of usage. An event sent late still counts, but only while it falls in one of these windows: send events as they happen.
Updated about 4 hours ago
