Verified visitors

If your visitors are already logged in to your site, tell Doorwick who they are. You get real names in the inbox instead of 'Visitor #418', and history that follows them across devices.

What you get

By default a visitor is anonymous: the widget stores a device token in their browser, which is enough to keep one conversation going on one device and nothing more. Clear the browser data and they're a stranger again.

Identify them and the contact gets their name and email, a green in the inbox, and a stable identity — the same person on their laptop and their phone is one contact with one history.

Why there's a signature

Your site key is public — it's in your page source by design. If claiming an identity only took an id, anyone could open the console, claim to be user_42, and read that person's support history.

So the host page must present a signature: an HMAC-SHA256 of the user's id, keyed with your site key's secret. Only your server has the secret, so only your server can vouch for who someone is. Doorwick recomputes it and rejects anything that doesn't match.

Compute the HMAC on your server, never in the browser. Shipping the secret to the page defeats the entire mechanism — at that point anyone can sign anything.

Getting your site key secret

Both live in the dashboard under Site keys, which is owner-only. The public key (ck_…) is on show; the secret sits behind a Reveal secret button, with a copy button once it's open and a Hide to put it away again.

It's revealed on request rather than printed on the page so it doesn't end up in your browser history, a screenshot, or anyone's shoulder-surfing range by default. Store it the way you'd store any other credential — an environment variable or your secret manager, never in the repository.

Sign the identity on your server

Hash the same id you'll send as external_id, keyed with the secret. In Ruby:

app/helpers/chat_widget_helper.rb
def doorwick_identity
  return nil unless current_user

  {
    external_id: current_user.id.to_s,
    email: current_user.email,
    name: current_user.name,
    hmac: OpenSSL::HMAC.hexdigest(
      "SHA256", ENV["DOORWICK_SITE_SECRET"], current_user.id.to_s
    )
  }
end

In Node:

identity.js
import { createHmac } from 'node:crypto'

export function doorwickIdentity(user) {
  return {
    external_id: String(user.id),
    email: user.email,
    name: user.name,
    hmac: createHmac('sha256', process.env.DOORWICK_SITE_SECRET)
      .update(String(user.id))
      .digest('hex'),
  }
}

Hand it to the widget

Set window.doorwickSettings before the widget script tag. Render it only for logged-in users — leave it out entirely for anonymous ones and they stay anonymous, which is correct.

app/views/layouts/application.html.erb
<% if (identity = doorwick_identity) %>
  <script>window.doorwickSettings = <%= identity.to_json.html_safe %>;</script>
<% end %>
<script async src="https://chat.doorwick.com/widget.js?v=3"
  data-site-key="<%= ENV["DOORWICK_SITE_KEY"] %>"></script>
</body>

external_id and hmac are required; email and name are optional but they're what turns the inbox from ids into people. The widget upgrades the session as soon as it boots — no extra call from you.

How identities merge

When someone identifies, Doorwick looks for an existing contact in the workspace with that external_id. If one exists, that's the canonical contact and the session moves onto it, bringing their history along. If not, the anonymous contact they're already using is promoted in place — so the conversation they started before logging in isn't orphaned.

Use your database's own stable primary key as external_id. An email address changes; a user id doesn't, and identity should survive someone updating their profile.

Checking it worked

Send a message as a logged-in user and open the thread. The header should show their real name with a green next to it. If it shows Visitor #123, the signature didn't match — almost always because the id being hashed isn't byte-for-byte the id being sent (a number on one side and a string on the other is the classic).

Next