FinTruvix

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 they land inside already signed in. It is about twenty lines on your side, and it needs no FinTruvix API credential at all.

Landing URL
https://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

Four steps. Only the second one is yours to build.

  1. 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.

  2. 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

  3. 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 any session is created.

  4. They land on their dashboard

    No second password and no second account: an existing client is matched, and a first-time visitor is created from what your CRM already holds about them.

Before you start

The signing key

A shared secret, and deliberately not on this page.

  1. 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.

  2. 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.

  3. 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.

Fields in the sign-in ticket claims
FieldRequiredWhat 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. 1 JSON-encode the claims.
  2. 2 base64url-encode that JSON — standard base64, then + becomes -, / becomes _, and any trailing = padding is removed. Call the result p.
  3. 3 HMAC-SHA256 the string p with the signing key, keeping the raw binary digest, and base64url-encode that the same way. Call it s.
  4. 4 The ticket is p, a dot, then s. Redirect to the landing URL with it as the t query 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://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 jti arrived 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 sub against your own API and either did not find it, or your CRM reports the client as a lead or as disabled. That last one is correct behaviour rather than a fault: the client is told to contact you.

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, because you have already verified them — and no client’s trading capital ever leaves their account with you.