綺和美
KIWABI · Concierge Integration

Concierge × Tangent Quiz — Integration Guide

For Tangent engineering · v1.0 · July 8, 2026 · back to the overview

This guide is everything your team needs to connect the AI Hair Test to the KIWABI Concierge. There are two integration paths, meant to be shipped in order: a client-side bridge for same-visit personalization (fast, no backend), then a webhook for durability and returning visitors. Start with Section 2, the decisions we need from you, since the rest depends on those answers.

Contents
  1. Overview & responsibilities
  2. Decisions we need from you
  3. Phase A — Client-side bridge
  4. Phase B — Webhook
  5. Webhook signing (with test vector)
  6. Identity & matching
  7. Consent, privacy, ownership
  8. Testing & go-live checklist
  9. Rollout sequence

1Overview & responsibilities

Goal: when a customer completes the AI Hair Test, the Concierge receives their result and personalizes the conversation. The quiz stays yours; the Concierge stays ours; the two connect through a small, well-defined contract.

Tangent buildsKIWABI builds
Emit a completion signal on the page (event or postMessage)Listen for it and pass the result into the Concierge
POST a signed webhook to our endpoint on each completionVerify, store, and serve the profile to the Concierge
Include a join key + a consent flag in the payloadMatch the submission to a Concierge session and personalize

2Decisions we need from you

Answer these first; they determine the contract.

3Phase A — Client-side bridge

Both widgets live on the same KIWABI page, so the fastest win needs no backend: when the quiz finishes, hand the result straight to the Concierge in the browser. Recommended contract (Tangent emits, KIWABI listens):

// Tangent, on completion — dispatch a CustomEvent:
window.dispatchEvent(new CustomEvent('tangent:quiz_complete', { detail: {
  submissionId: 'tan_9f2...',
  email: 'jane@example.com',      // if consented
  profile: { hairType: 'wavy', concern: 'thinning', segment: 'thinning-wavy' },
  recommendations: ['Root Vanish'],
  quizId: 'ai_hair_test', version: '3',
  completedAt: '2026-07-08T17:05:00Z'
}}));
// KIWABI, listening (already implemented on our side):
window.addEventListener('tangent:quiz_complete', (e) => {
  window.KiwabiConcierge?.setContext({ source: 'ai_hair_test', ...e.detail });
});
If the quiz runs in an iframe, send window.parent.postMessage({type:'quiz_complete', payload:{...}}, 'https://kiwabi.com') instead, and tell us your exact iframe origin so we verify it.
Trust boundary. Bridge data arrives in the browser and is user-forgeable, so the Concierge treats it as a same-visit hint for immediate UX, never as an authoritative fact for anything stored or claim-bearing. The signed webhook (Section 4) is the source of truth.

4Phase B — Webhook

POST https://<concierge-host>/api/quiz/tangent  ·  Content-Type: application/json
We will give you the exact host and a shared secret for staging and production.

Send one request per completed submission. Delivery is at-least-once: we deduplicate on submission_id, so retries are safe.

Payload

{
  "submission_id": "tan_9f2...",
  "occurred_at": "2026-07-08T17:05:00Z",
  "quiz": { "id": "ai_hair_test", "version": "3" },
  "identity": {
    "email": "jane@example.com",
    "shopify_customer_id": null,
    "session_id": "sess_abc",
    "visitor_id": "vis_123"
  },
  "answers": [
    { "id": "hair_type", "q": "Hair type?",    "a": "wavy" },
    { "id": "concern",   "q": "Main concern?", "a": "thinning" }
  ],
  "derived": { "segment": "thinning-wavy", "recommendations": ["Root Vanish"] },
  "consent": { "marketing": true, "at": "2026-07-08T17:04:40Z" },
  "market": "US", "locale": "en-US"
}
FieldTypeReqNotes
submission_idstringyesUnique per submission. Idempotency key.
occurred_atISO 8601yesCompletion time (UTC). We order by this.
quiz.idstringyese.g. ai_hair_test.
identity.emailstringcond.Primary cross-session join key. Send if consented.
identity.session_idstringrec.Correlates to the on-site session for same-visit matching.
answers[]arrayyesEach: {id, q, a}.
derivedobjectnoSegment and product recommendations if you compute them.
consent.marketingboolyesWhether the user consented to this use.
market, localestringnoe.g. US, en-US.

At least one of identity.email or identity.session_id must be present for personalization.

Responses & retries

StatusMeaning
200{"ok": true} — accepted (processed async).
401Missing or invalid signature.
400Malformed JSON or missing required field.
5xxOur error. Please retry.

On any non-2xx, retry with exponential backoff (for example 1m, 5m, 30m, 2h, 6h, then give up after ~24h). Because we dedupe on submission_id, duplicate deliveries are harmless.

5Webhook signing

Sign the exact raw request body bytes (do not re-serialize) with HMAC-SHA256 and the shared secret, hex-encode it, and send it in the header:

X-Tangent-Signature: sha256=<hex-hmac-sha256(secret, raw_body)>

We recompute and compare in constant time; a mismatch returns 401. All traffic is TLS only.

Test vector (verify your implementation reproduces this exact signature):
secretexample_secret_not_for_production
body{"submission_id":"tan_test_0001","occurred_at":"2026-07-08T00:00:00Z","quiz":{"id":"ai_hair_test"}}
signaturesha256=1418db424e2a198a08f2f0f9cd32823f9171106390d10ec8b1a035fc303e0f99
The real shared secret is delivered separately and is never placed in email, chat, or this document.

6Identity & matching

Same-visit matching is automatic through the Phase A bridge. For a returning visitor we match on a stable identifier (a Shopify login email, or a persistent first-party id); a per-visit session_id will not match across visits. We look up the latest submission for that identity and inject a compact profile into the Concierge's context. The Concierge personalizes from stored facts only: it never invents a hair profile, and if there is no quiz on file it simply asks. A submission with no stable identifier is kept for aggregate analytics but cannot personalize a return visit.

7Consent, privacy, ownership

Phase A stores nothing new: the data stays in the customer's own browser during their own visit. Phase B introduces stored personal data, so the items below are prerequisites before any customer data flows, not afterthoughts.

8Testing & go-live checklist

  1. We send you a staging webhook URL and a test HMAC secret.
  2. Confirm your signing matches the test vector above.
  3. Send a sample ai_hair_test submission to staging; we confirm 200 and that the profile is stored.
  4. Fire the client-side event on a staging page; we confirm the Concierge personalizes in the same visit.
  5. Verify a returning-visitor lookup by email works end to end.
  6. Confirm consent capture and the ownership/DPA note.
  7. We issue the production secret and host; you point production; we monitor the first live submissions together.

9Rollout sequence

  1. You: answer Section 2 (join key, completion signal, webhook capability, consent, environments).
  2. Us: share the staging endpoint + test secret securely, and confirm the exact event/origin.
  3. Phase A: you emit the completion signal; we wire the Concierge; test same-visit personalization.
  4. Phase B: you implement the signed webhook; we verify signatures and receipt on staging.
  5. Go live: production secret issued, endpoints switched, monitored together.

Phase A can go live on its own for an immediate win while Phase B is built. Nothing here blocks your quiz roadmap; the contract is additive.

← back to the plain-language overview