1. The idea in one sentence
Each time a page is displayed, your server builds a small token saying “this is Jane Smith, jane.smith@example.com”, signs it with a signing key that only Tikivio and you know, and puts it on the widget tag. Tikivio checks the signature: a token that was changed, is too old or was already used is refused, and the widget then falls back to asking for the address.
2. Generate the signing key
- An organisation administrator opens Settings › Widget › Settings, “Signing key” card (
/o/<your-organisation>/parametres/widget/reglages), and clicks “Generate the key”. - The key is shown only once, with a pre-filled sample to give your developer. Copy it into the server configuration (environment variable, secrets vault), never into a page, a browser script, a code repository or an e-mail.
- Check that the address of the site showing the widget (for example
https://erp.example.com) is in the widget’s allowed origins: otherwise the widget does not show at all.
3. Build the token
The token is built on the server, each time a page is displayed (it can be used only once):
text = email + "|" + name + "|" + expiry + "|" + nonce
signature = HMAC-SHA256(key = signing key, message = text), as lowercase hexadecimal
token = base64url(text, without trailing "=") + "." + signature
| Part | Rule |
|---|---|
email | address of the signed-in person (required) |
name | optional (leave empty: “email||…”), 120 characters at most, no “|” bar |
expiry | Unix time in seconds, 15 minutes at most from now (5 minutes recommended) |
nonce | 8 to 64 characters from A-Z a-z 0-9 _ -, drawn at random for each token |
Then add it to the widget tag, changing nothing else. Before:
<script async src="https://app.tikivio.com/widget/tikivio.js" data-tikivio="PUBLIC_ID"></script>
After (just one thing more, data-tikivio-token, recomputed on every display):
<script async src="https://app.tikivio.com/widget/tikivio.js" data-tikivio="PUBLIC_ID" data-tikivio-token="TOKEN"></script>
In a single-page application, call Tikivio.setHostToken(token) after sign-in instead. The token never goes into an address.
PHP
// Tikivio widget: sign the signed-in user's email, on your server only.
$key = getenv('TIKIVIO_SIGNING_KEY');
$name = str_replace('|', '', $user->name);
$exp = time() + 300; // 5 minutes (15 at most)
$nonce = bin2hex(random_bytes(12)); // new for every token
$payload = "$user->email|$name|$exp|$nonce";
$token = rtrim(strtr(base64_encode($payload), '+/', '-_'), '=')
. '.' . hash_hmac('sha256', $payload, $key);
Node.js (16 or later)
import { createHmac, randomBytes } from 'node:crypto';
// Tikivio widget: sign the signed-in user's email, on your server only.
const key = process.env.TIKIVIO_SIGNING_KEY;
const name = user.name.replaceAll('|', '');
const exp = Math.floor(Date.now() / 1000) + 300; // 5 minutes (15 at most)
const nonce = randomBytes(12).toString('hex'); // new for every token
const payload = `${user.email}|${name}|${exp}|${nonce}`;
const token = Buffer.from(payload, 'utf8').toString('base64url')
+ '.' + createHmac('sha256', key).update(payload, 'utf8').digest('hex');
Python 3
import base64, hashlib, hmac, os, secrets, time
# Tikivio widget: sign the signed-in user's email, on your server only.
key = os.environ["TIKIVIO_SIGNING_KEY"].encode()
name = user.name.replace("|", "")
exp = int(time.time()) + 300 # 5 minutes (15 at most)
nonce = secrets.token_hex(12) # new for every token
payload = f"{user.email}|{name}|{exp}|{nonce}".encode()
token = (base64.urlsafe_b64encode(payload).rstrip(b"=").decode()
+ "." + hmac.new(key, payload, hashlib.sha256).hexdigest())
C# (.NET 6 or later)
using System.Security.Cryptography;
using System.Text;
// Tikivio widget: sign the signed-in user's email, on your server only (.NET 6 or later).
var key = Encoding.UTF8.GetBytes(Environment.GetEnvironmentVariable("TIKIVIO_SIGNING_KEY")!);
var name = user.Name.Replace("|", "");
var exp = DateTimeOffset.UtcNow.ToUnixTimeSeconds() + 300; // 5 minutes (15 at most)
var nonce = Convert.ToHexString(RandomNumberGenerator.GetBytes(12)).ToLowerInvariant();
var payload = Encoding.UTF8.GetBytes($"{user.Email}|{name}|{exp}|{nonce}");
// C#: ToLowerInvariant() is required, Tikivio only accepts a lowercase signature.
var token = Convert.ToBase64String(payload).TrimEnd('=').Replace('+', '-').Replace('/', '_')
+ "." + Convert.ToHexString(HMACSHA256.HashData(key, payload)).ToLowerInvariant();
4. It does not work: common errors
When the token is refused, the widget does not block the person: it asks for their address, as without a token. The browser console (Network tab, /session/hote call) gives the reason:
| Response | Most frequent cause |
|---|---|
host_disabled | no signing key has been generated for this widget |
host_token_invalid | wrong signature: old key, space or line break copied with the key, signature in capitals, signed text different from the encoded text, name containing “|”, malformed e-mail address, or expiry more than 15 minutes ahead |
host_token_expired | token cached in the page, or server clock running late: synchronise it (NTP) and build the token on every display |
host_token_replayed | the same token was already used (cached page, fixed nonce): a new nonce for every token |
The widget does not show at all? It is not the token: the site address is missing from the allowed origins, or the widget is turned off.
5. Renew the signing key
Renew it if you think it may have leaked, or when the person who kept it leaves the team. “Regenerate the key” makes the old one unusable immediately: tokens signed with it are refused and sessions opened by your site are closed (those opened with an e-mail code remain). So prepare the configuration change on your server, regenerate, then put the new key in place straight away. In between, the widget keeps working: it simply asks for the address and a code, as without a token.
6. What the token proves, and what it does not
The token proves that your server declared this person: Tikivio trusts your site for the e-mail address it sends. Only put in the address of the person actually signed in. The other widget rules (limits, attachments, visible requests) stay the same; the full technical detail is in Install the widget.