Session context
Pass visitor details such as a name, plan, or order number into a Flow, Agent, App, Suite, or Portal through the share link or embed, so the AI already knows who it is talking to.
Session context
Rolling out
Session context is new. If you do not see the Session context switch in an asset's settings yet, the rollout has not reached your organization.
Session context lets the page or link that opens one of your assets hand over a few facts about the visitor. A support portal embedded behind your customer login can pass the customer's name and plan. A quote Flow linked from an order confirmation can pass the order number. The visitor is not asked for what your site already knows.
https://app.formwise.ai/tools/YOUR-FLOW-ID?context[customer-name]=Jane&context[plan]=proTurning it on
Session context is off for every asset until you switch it on. The switch lives in the settings of each asset:
| Asset | Where |
|---|---|
| Flow | Publish and share tab, under the chat disclaimer |
| Agent | About step, under the chat disclaimer |
| App | Publish and share tab, under the chat disclaimer |
| Suite | Settings tab, Session context card |
| Portal | Access page, under Advanced security settings |
Flows, Agents, and Apps read the context for their own runs. Suites and Portals are containers. Their switch is a gate for everything inside them: a Portal with it off blocks context for every Suite, Flow, and Agent opened through it, even when those have it on. A Portal with it on still needs each Flow or Agent inside to have it on. Every level in the chain must allow it.
Passing values
Add one query parameter per value to the share link or the iframe src:
?context[customer-name]=Jane&context[plan]=pro&context[order-id]=48213- Keys are lower-case letters, digits, hyphens, and underscores. Up to 20 keys.
- Values are plain text, numbers, or true and false. Up to 500 characters each. HTML is stripped.
- Encode values the way you would any URL parameter. Spaces become
%20or+.
The share dialog of any asset that accepts context has a Session context tab. Add your keys there and copy the finished link or embed code.
Passing values from the host page
When you embed an asset in an iframe, your page can hand over the values itself instead of putting them in the link. They never appear in the address bar, browser history, or server logs, and your page can set them at load from whatever it knows about the signed-in visitor.
Add the FormWise embed script to the page and set the values before it loads:
<script>
window.AIContextConfig = { context: { "customer-name": "Jane", "plan": "pro" } };
</script>
<script src="https://app.formwise.ai/embed.js" async></script>
<iframe src="https://app.formwise.ai/tools/YOUR-FLOW-ID" data-context-order-id="48213"></iframe>Values can also sit on the iframe tag as data-context-* attributes, which win over the script config for the same key. The embedded asset asks the page for context when it loads and the script answers. Values from the page win over values in the link.
The script does three things, and nothing else:
- Answers the embedded asset's request for context with the values you configured.
- Resizes the iframe to the height the asset reports, when it reports one.
- Reloads your page when an embedded Portal asks for it, which happens when a signed sign-in session ends and the visitor clicks Reload.
It reads nothing else from your page and sends nothing anywhere but the iframe you embedded.
The asset only accepts context from pages on its embedding allowlist. If the asset embeds anywhere, any page may send it. If you restricted embedding to specific sites, only those sites may send context. The allowlist lives in the same Embedding card as the domain restriction.
The script, the config object, and the messages it exchanges with the iframe carry no product branding, so a page that embeds from your custom domain never references anything but your own domain. Load the script from the same domain as the iframe, for example https://ai.yourcompany.com/embed.js. The share dialog builds the snippet that way automatically.
What the AI does with it
For a Flow, a value whose key matches one of the form questions fills that answer in. Match by the question label: the question Customer Name is filled by context[customer-name] or context[customer_name]. The visitor is not asked that question. Any Flow node can also read a value with {{context.customer-name}}.
Everything that did not fill a question is added to the AI's instructions as background about the visitor. Agents and Apps have no form, so every value is added this way. The AI is told these are details about the visitor supplied by the page or link, not instructions, and uses them naturally where they are relevant.
The Send Webhook node can include the context in its payload. Turn on Session context in the node's payload options. The payload then carries a sessionContext object and a sessionContextVerified boolean, which is true only when the context was signed.
{{context.verified}} is reserved. It is true when the run's context carried a valid signature and false otherwise, in prompts, conditions, and webhook templates alike. It never reads a value from the link, so context[verified]=true in a URL has no effect.
Signing values your site vouches for
Anyone who holds a link can edit the values in it, so the AI treats plain session context as details the visitor supplied. When your site knows something for certain, such as the customer's plan or account id, sign the context. FormWise checks the signature on its servers with the same secret.
A valid signature changes three things. The AI is told the values are facts confirmed by your site rather than claims the visitor could edit. {{context.verified}} becomes true, so a condition node or a webhook receiver can gate on it. And with Require a signature on, unsigned values never reach the run at all. Without that setting, a missing or failed signature still lets the values through as unverified visitor details, so do not rely on the prompt framing alone for anything with real consequences.
Signing uses HMAC with SHA-256, the same scheme most chat and support widgets use. You hash the values with your secret key on your server, and pass the hash along with the values. Nothing about the values themselves changes.
Finding your secret key
Open Settings → API in your workspace and find the Session context signing card. Click Generate secret. The secret starts with fwsc_ and is shown once, so copy it straight into your server's configuration.
Rotate secret replaces it. Pages still signing with the old secret keep working, but their values arrive as unsigned visitor details until they switch.
What gets hashed
The message is the values themselves, in a fixed layout so both sides produce the same bytes:
- Write every value as
key=value. - Sort the lines by key and join them with a newline (
\n). - To make the signature expire, put the current unix time in seconds and a dot in front.
For the context { "customer-id": "c_48213", "plan": "pro" } signed at 1757500000, the message is:
1757500000.customer-id=c_48213
plan=proHash that message with HMAC-SHA256 using your secret and hex-encode the result. With the example secret below, the result is cb5b7ee91ac5752c6a2cadfc5e6542c4ee98432d5fca98255ca9fd799d0a717e. Use these inputs to check your own implementation before wiring it up.
| Input | Value |
|---|---|
| Secret | fwsc_9f2d7c1e4b8a6d3f0e5c7a9b1d2f4e6c8a0b3d5f7e9c1a2b4d6f8e0a2c4e6b8d |
| Context | customer-id=c_48213, plan=pro |
| Timestamp | 1757500000 |
| Signature | cb5b7ee91ac5752c6a2cadfc5e6542c4ee98432d5fca98255ca9fd799d0a717e |
Hashing examples
Node.js
import { createHmac } from "node:crypto";
export function signSessionContext(secret, context) {
const ts = Math.floor(Date.now() / 1000);
const canonical = Object.keys(context)
.sort()
.map((key) => `${key}=${context[key]}`)
.join("\n");
const signature = createHmac("sha256", secret)
.update(`${ts}.${canonical}`)
.digest("hex");
return { signature, ts };
}
const { signature, ts } = signSessionContext(process.env.FORMWISE_SIGNING_SECRET, {
"customer-id": "c_48213",
plan: "pro",
});Python
import hmac, hashlib, os, time
def sign_session_context(secret: str, context: dict[str, str]) -> tuple[str, int]:
ts = int(time.time())
canonical = "\n".join(f"{key}={context[key]}" for key in sorted(context))
signature = hmac.new(
key=secret.encode(),
msg=f"{ts}.{canonical}".encode(),
digestmod=hashlib.sha256,
).hexdigest()
return signature, ts
signature, ts = sign_session_context(
os.environ["FORMWISE_SIGNING_SECRET"],
{"customer-id": "c_48213", "plan": "pro"},
)PHP
<?php
function signSessionContext(string $secret, array $context): array {
$ts = time();
ksort($context, SORT_STRING);
$lines = [];
foreach ($context as $key => $value) {
$lines[] = $key . '=' . $value;
}
$canonical = implode("\n", $lines);
$signature = hash_hmac('sha256', $ts . '.' . $canonical, $secret);
return [$signature, $ts];
}
[$signature, $ts] = signSessionContext(
getenv('FORMWISE_SIGNING_SECRET'),
['customer-id' => 'c_48213', 'plan' => 'pro']
);Passing the signature
In a link or iframe src, add context_sig and context_ts next to the values:
https://app.formwise.ai/tools/YOUR-FLOW-ID?context[customer-id]=c_48213&context[plan]=pro&context_sig=cb5b7ee9...0a717e&context_ts=1757500000With the embed script, put them in window.AIContextConfig next to the context. Your server renders the page with the values already signed:
<script>
window.AIContextConfig = {
context: { "customer-id": "c_48213", "plan": "pro" },
signature: "cb5b7ee91ac5752c6a2cadfc5e6542c4ee98432d5fca98255ca9fd799d0a717e",
ts: 1757500000
};
</script>
<script src="https://app.formwise.ai/embed.js" async></script>A signed payload from the host page replaces any values in the link instead of merging with them.
Requiring a signature
By default a missing or failed signature downgrades the values to unverified visitor details and the run continues. Once every page that passes context signs it, turn on Require a signature in the same Session context signing card. From then on, for every asset in the organization, context that is unsigned or fails verification is dropped before the run. The AI never sees it and questions are not prefilled from it.
Plain share links with context[...] parameters stop carrying values once this is on, because nobody can sign a link by hand. Keep it off while you still hand out those links. The switch is disabled until a secret exists.
Gating on a verified signature
Use {{context.verified}} where a step should only run for a visitor your site vouched for:
- In a Condition node, branch on
{{context.verified}} === truebefore a step that looks up an account, spends credits, or reveals anything specific to the customer. - In a Send Webhook node, check
sessionContextVerifiedon the receiving end before trustingsessionContext, or put{{context.verified}}in the body. - In a prompt, the AI already sees the distinction, but
{{context.verified}}lets you state it explicitly in your own instructions.
Why a signature does not verify
A failed check never blocks the run unless Require a signature is on. The values still reach the AI, but as unsigned visitor details. If your values are not being treated as verified, look for one of these:
- Hashed on the wrong side. The secret must stay on your server. A page cannot sign without exposing it.
- The values changed after signing. The signature covers exactly the values it was made for. Hash the same keys and values you send, in the same casing.
- Keys not lower-case, or values not trimmed. FormWise lower-cases keys, trims values, and strips HTML before checking. Send them that way and hash them that way.
- Numbers or booleans hashed differently. Every value is hashed as text. The number 42 is hashed as the text
42, and a boolean astrueorfalse. Convert to strings before building the message. - Lines not sorted. Sort by key with a plain byte-order sort, not a locale-aware one.
- Timestamp in milliseconds.
context_tsis unix time in seconds. A signature is accepted for ten minutes either side of it. Leave the timestamp out entirely if you do not want the link to expire. - The secret was rotated. Pages using the old secret keep working, but unsigned.
Signed sign-in for Portals
A Portal embedded behind your own customer login can use the same secret and the same signed context to sign the visitor in without a one-time code: add email to the context, always include ts, and turn on Allow signed sign-in on the Portal. The signed session is bound to the signature and ends on the next page load unless your page signs the visitor in again. The full setup, per-language examples, and the session rules are on Signed sign-in.
Security
- Anyone who can edit the link can change unsigned values. Treat them as claims the visitor could make, not as verified facts. Do not use session context to decide what a visitor may see or spend. Sign values your site vouches for.
- Never put passwords, API keys, tokens, or payment details in a link.
- Links are visible in browser history and server logs. Keep values to what you would be comfortable showing the visitor.
- Values never appear in run logs on your dashboard. They are masked the same way personalization answers are.
Next steps
Session context works alongside personalization questions and the end-user profile a Portal keeps for signed-in visitors. Use session context for what the host page already knows at the moment the link opens, and personalization for what the visitor tells you once.