Integration guide
Add a Copy Trading button to your client portal.
This FinTruvix deployment serves your clients and nobody else. They never create an account here and never type a password: your client area vouches for them with a short-lived signed link, and one “Continue as” click signs them in. It is about twenty lines on your side, and it needs no FinTruvix API credential at all.
- Landing URL
https://fxbo.fintruvix.com/sso/fxbo?t=<ticket>- Issuer (
iss) fxbo- Signature
- HMAC-SHA256 over the encoded claims
- Ticket lifetime
- 120 seconds, single use
The journey
What the button does
Five steps. Only the second one is yours to build.
-
A client clicks in your client area
They are already authenticated with you, so you already know who they are. That is the fact this whole design turns on, and it is the one thing FinTruvix cannot establish on its own.
-
Your handler signs a ticket
A small page in your client area reads the signed-in client’s id, builds the claims below, signs them with the shared key, and redirects to the landing URL with the ticket in the query string.
Built by: You
-
FinTruvix verifies it, then checks with you again
The signature, the audience, the issuer, the age and the single-use id are all checked. Then, using our own read-only API credential, we ask your CRM to confirm the client exists and is active — before anybody is signed in. The client is then matched to their FinTruvix account, or one is created the first time from what your CRM already holds about them.
-
They confirm on a “Continue as” page
FinTruvix shows the account’s name and email address, and warns them if somebody else is signed in on this browser. One press signs them in. This is what stops a link somebody else sends from signing a person into the wrong account. A client who is already signed in here as themselves skips this step.
-
They land on their dashboard
No second password and no second account. Their name and email are copied from your record every time they arrive, so change them in your CRM rather than here.
Before you start
The signing key
A shared secret, and deliberately not on this page.
-
Ask your FinTruvix contact
It is generated in the FinTruvix Control Tower and shown there once, so it is handed to you directly rather than being readable from anywhere afterwards — including here. Agree a channel that is not email first.
-
Treat it as a database password
Anyone holding it can sign in as any of your clients. It belongs in your server-side configuration, and must never reach a browser, a repository or a log.
-
Replacing it is planned, not sudden
A routine rotation comes with a deadline: the old key keeps working until then, so you can deploy the new one when it suits you and no client is interrupted. A key believed to have leaked is replaced with no window.
Tell us: Once the new key is live
The ticket
The claims, field by field
This is exactly what the verifier reads. A field marked required is refused when it is missing, empty or wrong.
| Field | Required | What we check |
|---|---|---|
sub |
Yes | The client’s user id in your CRM, as a string, and the same id your API returns for them. Must not be empty. We look it up with you before signing anybody in. |
iss |
Yes | Must be exactly fxbo, which is also the last segment of the
landing URL. Anything else is refused. |
aud |
Yes | Must be exactly fintruvix. It is what stops a ticket minted for another
system being replayed at this one. |
iat |
Yes | Issued-at, in whole seconds since the Unix epoch. Refused if it is more than
30 seconds in the future — two clocks are involved and neither
is ours, so there is a small tolerance and no more. The ticket also dies
120 seconds after this moment, whatever exp says. |
exp |
Yes | Expiry, same format, and must still be in the future. Set it to
iat + 120: a longer value does not widen the window,
because the platform applies its own limit as well as yours. |
jti |
Yes | A random, unguessable id, unique to this ticket — 16 random bytes as hex is ideal. It is recorded on use, so a ticket that arrives twice is refused the second time. |
email_sha256 |
Optional | Lower-case hex SHA-256 of the client’s email address. If you send it, we check
it against the address your CRM reports for sub and refuse a mismatch.
Recommended: it binds the ticket to one client twice over, so a mistake that put the
wrong id in sub is refused rather than opening somebody else’s
account. |
v |
Not checked | A format version. Send 1 — it is part of the agreed shape and we
may start reading it — but nothing today refuses a ticket for the value it
carries, or for leaving it out. |
Any other field you add is ignored. It is still covered by the signature, so adding one cannot break a ticket — but nothing reads it, so do not use one to carry a fact you need us to act on.
Encoding
Building the ticket
Two base64url segments joined by a dot. No padding.
This is the same shape as a JWT and deliberately not one: there is no header segment and only HMAC-SHA256 is ever accepted, so there is no algorithm field for an attacker to change.
- 1 JSON-encode the claims.
- 2 base64url-encode that JSON — standard base64, then
+becomes-,/becomes_, and any trailing=padding is removed. Call the resultp. - 3 HMAC-SHA256 the string
pwith the signing key, keeping the raw binary digest, and base64url-encode that the same way. Call its. - 4 The ticket is
p, a dot, thens. Redirect to the landing URL with it as thetquery parameter.
Sign p itself — the encoded
segment, character for character, exactly as it will appear in the link — not the JSON it
decodes to. We verify against the bytes we received, so a signature taken over a re-encoding of
the same claims will not match.
A worked example, in PHP. Any language with HMAC-SHA256 will do.
<?php
// From your FinTruvix contact. Server-side configuration only.
$key = getenv('FINTRUVIX_SSO_KEY');
$now = time();
$claims = [
'v' => 1,
'iss' => 'fxbo',
'aud' => 'fintruvix',
'sub' => (string) $client->id, // your client id
'iat' => $now,
'exp' => $now + 120,
'jti' => bin2hex(random_bytes(16)),
'email_sha256' => hash('sha256', $client->email), // optional, recommended
];
$b64url = static fn (string $raw): string =>
rtrim(strtr(base64_encode($raw), '+/', '-_'), '=');
$encoded = $b64url(json_encode($claims));
$ticket = $encoded . '.' . $b64url(hash_hmac('sha256', $encoded, $key, true));
header('Location: https://fxbo.fintruvix.com/sso/fxbo?t=' . $ticket);
exit;
Mint the ticket at the moment the client presses the button. Never in advance, and never cache the finished URL: it is single-use and lives for 120 seconds.
Failure
If a link is refused
What the client sees, and what it usually means.
Clients see one deliberately vague sentence for every refusal a stranger could probe, so the page cannot be used to discover which client ids are real. To diagnose an integration problem, ask your FinTruvix contact to read the server log — each refusal is recorded there with a specific reason. Do not try to infer it from what the client saw.
- Signature — usually a base64url variant that kept its
=padding or its+/alphabet, or a signature taken over the JSON instead of the encoded segment. - Expired — the ticket was minted in advance, or the URL was cached. It is valid for 120 seconds.
- Replayed — the same
jtiarrived twice. Generate a fresh one on every press, including when a client presses the button again. - Clock — the host minting tickets is ahead of ours by more than 30 seconds. Run NTP on it.
- Unknown or inactive client — we confirmed
subagainst your own API and either did not find it, found a client whose record carries no email address, were not allowed to see it (your API answers 403 and 404 alike, so if every client is refused this way, check the API user’s permissions first), or your CRM reports the client as disabled. That last one is correct behaviour rather than a fault: the client is told to contact you. A lead is not refused — they sign in as normal, and can look around but cannot start a copy or have a strategy listed until you make them a full client.
Scope
What your clients can do here
Two things, and nothing else.
A strategist publishes a strategy and links one of their trading accounts to it. A follower searches the marketplace and copies an approved strategy into an account of their own. Identity checks stay with you — FinTruvix never asks a client for a document: every full client of yours is verified automatically, and a lead can look around but cannot start copying, and is not offered to followers as a strategist, until you convert them. No client’s trading capital ever leaves their account with you.
Switching a client off in your CRM — or turning a full client back into a lead — stops anything new: from then on they cannot start or resume a copy, and their strategies are not offered to new followers. Switching them off also signs them out of FinTruvix within minutes. Copies that are already running are not paused. Switching a follower’s trading account off as well takes it out of service and pauses the copies into it. A strategist’s account is never taken out of service automatically, because other people’s copies depend on it — FinTruvix staff are alerted and decide.