Without a per-user identifier a provider's abuse systems see only your API key, so the remedy lands on your account instead of on one customer. The field is `safety_identifier`, the value is a hash rather than an email, and unauthenticated traffic still needs a session ID.
One field that keeps your account out of trouble
When a provider's abuse systems flag traffic from your API key, they see your key. Without a per-user identifier they cannot tell one customer abusing your product from your product abusing the API, and the remedy lands on your account.
OpenAI's answer is a request field, safety_identifier: "a string that uniquely
identifies each user". Send it on every request and the provider can act on the
one user instead of the key.
This is cheap to add and easy to get wrong in a way that creates a privacy problem while solving an abuse one.
Hash it
The documentation is explicit: "hash the username or email address in order to avoid sending us any identifying information."
So the value is not the email. It is a stable hash of it, or an internal user ID that means nothing outside your database. Either satisfies the purpose, which is that the same person produces the same string every time, and neither hands a third party a customer list.
Two details follow from "stable". Salt the hash with something fixed, not per-session, or the same user becomes a new identifier on every login and the field stops working. And do not use a raw sequential ID if the count itself is commercially sensitive.
Unauthenticated users still need one
A product with no sign-in has no user ID, and sending nothing is the worst option, since all your anonymous traffic collapses into one undifferentiated stream.
Send a session ID. It is weaker than a user ID, because one person can produce many, and it is far better than nothing: abuse from one session gets attributed to that session rather than to your whole integration.
This connects directly to know your customers and use cases, which argues for authentication partly for this reason. An anonymous product carries risk a signed-in one does not.
On the Realtime API it is a header
Same identifier, different transport. The Realtime API takes
OpenAI-Safety-Identifier as a header, carrying the same stable value.
Worth noting because a team that adds the field to their text calls and forgets their voice feature has an unattributed surface, and it is the one where abuse is hardest to review after the fact.
Where to put it in your code
Set it once, at the boundary where you construct provider calls, not at each call site. A thin wrapper around your client that reads the authenticated user from request context and attaches the identifier means new features get it for free and nobody has to remember.
The same wrapper is the natural home for the feature label that LLM observability argues for, since both are per-request metadata that nothing in the API infers for you. Adding them together costs one code change instead of two.
What it does not do
The identifier is for attribution, not for authorization or rate limiting. It does not stop a user doing anything; it records who did it.
Your own limits still have to exist. Constraining inputs and outputs covers the caps that prevent misuse, and per-customer spend limits are a gateway concern, which is one of the arguments for Helicone.
Treat safety_identifier as the thing that makes an incident investigable and
survivable, and treat the limits as the thing that makes it rarer.
Further reading
- OpenAI, Safety best practices: the field, the hashing requirement, session IDs, and the Realtime header.
- Know your customers and use cases: why authentication is a safety control.
- Constraining inputs and outputs: the limits that prevent rather than record.
- LLM observability: the other per-request metadata nothing infers for you.
Knowledge check
Question 1 of 3
Sign in to save your progress and pick up where you left off.