Skip to the content
Menu: Case links from your CRM

Case links from your CRM or helpdesk

A case link is a personal link to your website avatar for one customer and one case: an order, a claim, an appointment, a support ticket. Your CRM or helpdesk makes it with the API and sends it by email, text message or your own app. When the customer opens it, the avatar greets them about their case with the details already there ("Hi Sam, I can see you're here about your order 10423…"), and your actions run for that customer, with the case's reference, without asking for it again.

You need:

  • the switch Send customers a personal link to their case on, under Settings → Features → Website and job interviews. It's off until an owner or admin switches it on, and comes with the Starter plan and up;
  • a portal whose website door is open to visitors: published, open to visitors, with a website subject live;
  • a key an owner or admin makes under More → API Keys, choosing scopes and ticking the case links scope (cases:write). Full access never includes it, a key can never pass it on, and an app or assistant connected to Zentiq can never have it: a case link lets whoever holds it talk to your avatar as that customer. Keep the key on your server.
case-links.ts
const ticket = await helpdesk.ticket("T-77");
try {
  const link = await zentiq.createCaseLink(
    PORTAL,
    {
      customerId: ticket.customerId, // your own id: every action call carries it as customer.id
      reference: ticket.number, // every action call carries it as case.reference
      kind: "ticket",
      summary: `The parcel is ${ticket.status}; the new delivery date is ${ticket.promised}.`, // read by the avatar as data
      facts: { status: ticket.status, promised: ticket.promised },
      firstName: ticket.firstName,
      email: ticket.email, // masked for the avatar; Verified customer's code goes here
      expiresInDays: 7,
      maxOpens: 5,
    },
    { idempotencyKey: `case-link-${ticket.id}` }, // a retry answers with the same link
  );
  // The address is only in this answer: send it now.
  await helpdesk.sendSms(ticket.customerId, `About your ticket ${ticket.number}: ${link.url}`);
  await helpdesk.note(ticket.number, `Case link ${link.id} sent, works until ${link.expiresAt}`);
} catch (e) {
  // case_links_off (the switch) or door_closed (the website door isn't open to visitors)
  if (e instanceof ConflictError) console.log(`No link: ${e.details?.reason}`);
  else throw e;
}
  • customerId is your own id for the customer, reference the case's own number in your system, and kind what the case is (order, claim, appointment, ticket, booking, application, renewal or case), for the avatar's words.
  • summary says what the case is about and where it stands, in a sentence or two the avatar may use with your customer. The avatar reads it, and facts, as your record: data, never instructions. A fact named like a password, a code or a card detail is refused.
  • firstName is how the avatar greets them. email is the address you have on file: the avatar only ever sees it masked (s***@example.com).
  • opening is the avatar's first line about the case, in your words. Without it, the avatar says what the case is, the latest from your summary, and asks how it can help.
  • expiresInDays (7 unless you say, 30 at most) and maxOpens, the conversations it may start (20 unless you say, 100 at most).

url is in this answer only: Zentiq keeps a hash of the link, never the link itself, and the case's details are kept encrypted and wiped as soon as the link stops working. The SDKs send an Idempotency-Key, so a retry answers with the same link; pass your own (your ticket's id) to keep it the same across restarts.

409 conflict says why no link was made in details.reason: case_links_off (the switch is off) or door_closed (the portal's website door isn't open to visitors). On a plan without case links it is 403 upgrade_required with details.reason plan_required and details.plan, the plan that includes them. A field the rules refuse is 400 invalid_request, naming it in param.

Your team can make them too: the portal's Actions tab, Case links, with an option to email the link to the customer in your organisation's name.

2. What your customer sees#

The link opens your portal's website door with one line about what the conversation is for ("Hi Sam, this conversation is about your order 10423."), never the case's details. The avatar's first line is about the case. The AI label and the start notice are there as for any visitor, and the avatar says it's an AI whenever asked.

Opening the page uses nothing: each conversation started from the link uses one of its maxOpens, so a link that is forwarded, or opened by an email or chat app's preview, can't be used again and again. An expired, revoked or used-up link, or one made for another portal, shows a plain page with the way to your normal website door. A case link works on the full page of a website door only: the website widget and a builder's preview ignore it.

A case link is recognition, not a full identity check: whoever holds it is taken for your customer. For anything sensitive, switch on Verified customer ("Ask customers for a one-time code before sensitive account actions"): the code goes to the email on the link, and the avatar goes on only once they type it.

3. Your actions get the customer and the case#

Every call the avatar makes to your system from that conversation carries both:

JSON
{
  "action": "look_up_order",
  "arguments": { "order_number": "10423" },
  "customer": { "id": "cus_4821", "verified": true },
  "case": { "reference": "10423", "kind": "order" },
  "test": false
}

A GET carries zentiq_case=10423. Answer only about that customer's own records, as for a signed-in customer. The avatar may fill an order, booking, claim, ticket or case number from the case's reference; anything else comes from what the customer says. If they may be under 18, the case is taken out of the conversation and no longer sent.

case-links.ts
const hook = await zentiq.createWebhook({ url: "https://helpdesk.example.com/zentiq/webhooks", events: ["case_link.opened"] });

// In your receiver (see "Receiving webhooks"): the link, your reference and your customer id; never the case's details
// (its summary only to a webhook made with includeConversation).
async function onDelivery(rawBody: string, headers: Record<string, string>) {
  const event: ZentiqWebhookEvent = verifyWebhook(rawBody, headers, hook.secret);
  if (event.type === "case_link.opened") {
    const { caseReference, customerId, opens, maxOpens, channel } = event.data;
    await helpdesk.note(caseReference ?? "", `Customer ${customerId} is talking to the assistant (${channel}), conversation ${opens} of ${maxOpens}.`);
  }
}

case_link.opened carries the link (caseLinkId), your caseReference and customerId, how many conversations it has started (opens of maxOpens) and the channel (voice or chat). The case's summary goes only to a webhook made with includeConversation, and never for a visitor who may be under 18. In the event log (listEvents), these events need cases:write beside events:read.

case-links.ts
// The ticket was solved: the link stops working at once and its details are wiped.
for await (const l of zentiq.paginate("listCaseLinks", {}, { params: { id: PORTAL } })) {
  if (l.reference === ticket.number && l.status === "active") await zentiq.revokeCaseLink(l.id);
}

Revoking stops the link at once and wipes its details; a conversation it already started goes on. Listing, reading (getCaseLink) and revoking work while the switch is off. A link's record is deleted 30 days after it stops working.

From your Shopify order emails#

A Shopify store can put a case link in each order's own emails, with no code: every order gets a link that its shipping and delivery emails print ("Questions about your order? Talk to us face to face"), and the avatar greets the customer about that order, which your Shopify actions can look up.

You need: case links on; Put a personal case link in your Shopify order emails on (Settings → Features → Website and job interviews, Plus and up); Connect your own systems on, with your store connected in the portal's Actions → Connect a system → Shopify. The connection uses your own Shopify app: one made in Shopify's Dev Dashboard (its Client ID and Client secret), or an older app made in the Shopify admin (its Admin API access token and API secret key), with the permissions read_orders, write_orders, read_fulfillments and read_products.

  1. In the portal's Actions, under Shopify order emails, switch it on for the portal. Zentiq subscribes your store to its order notifications, which carry only the order's id, and checks every one's signature.
  2. Copy the snippet shown there into Shopify: Settings → Notifications → Customer notifications, open Shipping confirmation, choose Edit code, paste it where the line should go, and save. Do the same for Shipping update, Out for delivery and Delivered. A version for text-message notifications is there too.
  3. Add Look up an order from your Shopify connection, and switch on Verified customer: the one-time code goes to the order's email before anything sensitive.

Each order gets one link, written on the order (its zentiq.case_link field), lasting 30 days and opening at most 20 conversations. Later emails renew it when fewer than 10 days are left (the old one keeps working until it expires), and what the avatar knows follows the order as it ships. The Order confirmation usually goes out before Shopify tells Zentiq about the order, so there the line points to your normal website page, where the avatar looks the order up with its number and the email on it. A link stops when the order is cancelled as fraud or a declined payment, when you revoke it, or when you disconnect the store. These links are listed with the others, with source: "api", customerId order-<Shopify order id> and the order's name (without "#") as the reference.

What never leaves Zentiq#

  • The case's details are never in the address (only a signed token), never in an answer and never in the list.
  • The email is only ever shown to the avatar masked, and a case link's email never reaches your webhooks.
  • A link made for one organisation's or portal's door never opens another.