Developers

Context API

When a customer writes in, your agents shouldn't have to ask who they are or what went wrong. The Context API sends that from your own systems, and Frictionless shows it beside the chat, on the website and on WhatsApp.

Last updated 14 September 2026

How it works

  • Contacts are your users, keyed by your own user ID. They carry a name, email, phone number and any attributes you like, such as a plan or a verification status.
  • Events are things that happened to a contact, like “Transfer failed”. They appear on the customer’s timeline, and as a card in any chat with them from the last 24 hours.
  • A contact’s phone number links them to their WhatsApp chats, even ones that start later. A signed identify() links them to their website chats.

There are two ways in. Your server can call the REST API, which agents can trust. Your website can use the chat widget, where identity is signed by your server and everything else is shown as unverified.

Before you start

  1. In Frictionless, open Settings and find Developers. Only workspace owners see it.
  2. Copy your Workspace ID. It’s public, and goes in the website snippet.
  3. Create a secret key (fk_live_…) and store it as an environment variable on your server. It’s shown once. Never put it in a web page or a mobile app.

From your server (REST API)

Base URL https://api.usefrictionless.com/api/v1. Send JSON with your secret key in the Authorization header. The API only accepts calls from servers; browsers are blocked so the key can’t leak through a web page.

Create or update a contact

PUT /contacts/:userId. Only the fields you send change. Set an attribute to null to remove it.

curl -X PUT https://api.usefrictionless.com/api/v1/contacts/user_42 \
  -H "Authorization: Bearer $FRICTIONLESS_SECRET_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Ada Obi",
    "email": "ada@example.com",
    "phone": "+2348012345678",
    "attributes": { "plan": "Business", "kycVerified": true, "walletBalance": 45000 }
  }'

Returns the contact, and linkedChats: how many chats it’s now linked to.

Send an event

POST /events. Say who it happened to with userId (best: saved even before they ever chat, and creates the contact if needed), with phone (for someone already chatting on WhatsApp), or both. amount and reference get their own rows on the card.

curl -X POST https://api.usefrictionless.com/api/v1/events \
  -H "Authorization: Bearer $FRICTIONLESS_SECRET_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "userId": "user_42",
    "event": "Transfer failed",
    "properties": { "amount": "NGN 45,000", "reference": "TRX_0001", "reason": "Insufficient funds" }
  }'

Returns 201 with the event’s id and postedToConversations: how many active chats received a card. occurredAt (ISO 8601) is optional and defaults to now.

In Node.js

async function sendEvent(userId, event, properties) {
  const res = await fetch("https://api.usefrictionless.com/api/v1/events", {
    method: "POST",
    headers: {
      Authorization: "Bearer " + process.env.FRICTIONLESS_SECRET_KEY,
      "Content-Type": "application/json",
    },
    body: JSON.stringify({ userId, event, properties }),
  });
  if (!res.ok) {
    const { error } = await res.json();
    throw new Error(error.code + ": " + error.message);
  }
  return res.json();
}

await sendEvent("user_42", "Transfer failed", { amount: "NGN 45,000", reference: "TRX_0001" });

Read or delete a contact

GET /contacts/:userId returns the contact and its latest 20 events, which helps when checking an integration. DELETE /contacts/:userId removes the contact and its events, for example when a user deletes their account with you. Chats and messages stay.

On your website (chat widget)

Add the chat snippet if you haven’t, then tell it who is signed in.

<script src="https://usefrictionless.com/widget/v1.js"></script>
<script>
  Frictionless.init({ company_id: "YOUR_WORKSPACE_ID" });

  // Only for signed-in users. userHash comes from your server (below).
  Frictionless.identify({
    userId: "user_42",
    userHash: "HASH_FROM_YOUR_SERVER",
    name: "Ada Obi",
    email: "ada@example.com",
  });

  // Something that just happened on the page
  Frictionless.track("Checkout failed", { amount: "NGN 45,000", reference: "ORD_981" });
</script>

Computing userHash

userHash is the HMAC-SHA256 of the user ID, keyed with your secret key, as lowercase hex. Compute it on your server when you render the page, so nobody can pretend to be another user by editing it.

// Node.js
const crypto = require("crypto");
const userHash = crypto.createHmac("sha256", process.env.FRICTIONLESS_SECRET_KEY).update(user.id).digest("hex");
# Python
import hmac, hashlib, os
user_hash = hmac.new(os.environ["FRICTIONLESS_SECRET_KEY"].encode(), user_id.encode(), hashlib.sha256).hexdigest()
// PHP
$userHash = hash_hmac('sha256', $user->id, getenv('FRICTIONLESS_SECRET_KEY'));

identify() accepts a name and email but not a phone number. Send phone numbers from your server, so a visitor can’t attach themselves to someone else’s WhatsApp chat.

track() and setContext() run in the visitor’s browser, where anyone can change them, so agents see them marked “not verified”. Send anything agents must rely on, such as balances or payment outcomes, from your server.

Limits

WhatLimit
userId1 to 128 letters, numbers or _ . : @ + -
Attribute and property keysUp to 40 letters, numbers, spaces, dots, dashes or underscores
ValuesText up to 500 characters, numbers, or true/false. No nested objects.
Attributes per contact50 per request, 100 in total
Properties per event20
Event name120 characters
Phone7 to 15 digits with the country code, like +2348012345678
Requests300 a minute per secret key

Errors

Errors come back as { "error": { "code": "...", "message": "..." } }. The message says what to fix.

StatusCodeMeaning
400invalid_request, invalid_user_idSomething in the request is missing or the wrong shape.
401missing_key, invalid_keyNo secret key, or one that was rotated.
404contact_not_found, customer_not_foundNo contact with that userId, or nobody with that phone has chatted yet.
429rate_limitedOver 300 requests a minute. Wait and retry.
500server_errorOur side. Retry with a short backoff.

Keeping it safe

  • Keep the secret key on your server. If it ever leaks, rotate it in Settings; the old key stops working at once.
  • Rotating also changes every userHash, so deploy the new key to your server straight away.
  • Send only what agents need to help. You are responsible for having a lawful basis to share your users’ data with Frictionless; see our privacy policy for how we handle it.