Guide: showing a user their own history
Let a signed-in person see what they agreed to and when, by calling the server-side history endpoints from your backend.
A “Your privacy choices” page that lists what someone agreed to, when, and how they changed their mind builds trust — and answers the most common data-access question without a ticket. The history is already in the vault; you only need to read it and show it.
sk_ endpoints for the user who is logged in, then sending the browser only what you want it to show. For a complete, formal copy of everything held about a person, use a data-access request instead (Vault & data requests).The two endpoints
| Endpoint | Returns | Filter |
|---|---|---|
GET /v1/subjects/{ref}/consent/history | Every consent record, newest first (max 500) | ?purpose=<key> |
GET /v1/subjects/{ref}/preferences/history | Every preference write, newest first (max 500) | ?field=<key> |
Both need a secret (sk_) key — with the subjects:read scope, if you have restricted the key’s scopes — and {ref} is the person’s identifier, for example external_id:u_9241 (URL-encode it). Reading never creates a subject: an unknown one is a 404.
Call it from your backend
curl -H "Authorization: Bearer $TC_SECRET_KEY" \
"https://api.tripticonsent.tripticode.com/v1/subjects/external_id%3Au_9241/consent/history?purpose=marketing_email"import { TripticonsentClient } from '@tripticonsent/sdk';
const tc = new TripticonsentClient({
baseUrl: 'https://api.tripticonsent.tripticode.com',
apiKey: process.env.TC_SECRET_KEY!, // sk_live_… — server only
});
// e.g. inside GET /api/my-privacy-history, after you authenticate the request
export async function myHistory(userId: string) {
const subject = 'external_id:' + userId; // NEVER take this from the request
const [consent, prefs] = await Promise.all([
tc.getConsentHistory(subject),
tc.getPreferenceHistory(subject),
]);
return { consent, prefs }; // …or map them to just the fields your UI shows
}What comes back
Consent history: { subjectId, count, legalBasis, records }. legalBasis maps each purpose key to its basis. Each record has:
purposeKey,purposeVersion,state(GRANTED,DENIED,WITHDRAWNor system-writtenEXPIRED)collectedAt,expiresAt,channel,locale,pageUrlsource—API(the person acted),IMPORT/MIGRATION(you asserted it), orCONSOLE(written by the system, such as an expiry)metadata— whatever you sent with the write, returned as-isproof— the hash of the exact statement the record was given under;supersedesId— the record it replaced
Preference history: { subjectId, count, records }, each record with fieldKey, value, templateVersion, collectedAt, source, channel, pageUrl, supersedesId and confirmationState. Pending double opt-in requests are included — confirmationState is PENDING until the person clicks the link (then CONFIRMED, or EXPIRED / CANCELLED) — so filter or label them if you only want confirmed choices.
What is not returned
- IP addresses and user agents — they stay in the vault and in the data-access export. The double opt-in
proofin preference history is trimmed to its non-personal fields (doubleOptIn,confirmationId,requestedAt,method). - Other people’s data — a history is scoped to one subject (and follows merges into it).
- Anything beyond the newest 500 rows per call; filter by
purposeorfieldfor long histories. - Receipt ids — history rows don’t carry them. Keep the
receiptIdreturned when you recorded the consent if you want to offer a downloadable proof (GET /v1/receipts/{id}).
metadata is returned verbatim, so only put in it what you would be comfortable showing back to the person — or don’t pass it through to your UI. Show source in plain words (“You chose this on the cookie banner” / “Recorded by Acme from your existing contract”), and translate purposeKey into the purpose’s own title with GET /v1/purposes.