Skip to main content
A common pattern: you want to ask certain users for feedback, but once they’ve completed the interview, stop showing the prompt. This guide walks through the full integration — from checking capacity, to opening the interview, to handling the webhook that tells you it’s done.

What you’ll build

  1. Check if your experiment is accepting interviews before showing the prompt
  2. Open the interview in a new tab when the user clicks
  3. Receive a webhook when the interview completes
  4. Mark the user in your database so the prompt doesn’t appear again

Prerequisites

  • A userjourneys.ai account with an active experiment
  • An API key
  • A server that can receive HTTPS POST requests (for the webhook)

Step 1: Configure the webhook

Set up a webhook so userjourneys.ai notifies your server when an interview completes. You only need to do this once per project.
curl -X PUT https://app.userjourneys.ai/api/v1/webhooks \
  -H "Authorization: Bearer uj_live_your_key_here" \
  -H "Content-Type: application/json" \
  -d '{
    "url": "https://your-app.com/webhooks/userjourneys",
    "events": ["interview.completed"]
  }'
The response includes a signing_secret — save it. You’ll need it to verify incoming webhooks.
{
  "url": "https://your-app.com/webhooks/userjourneys",
  "events": ["interview.completed"],
  "signing_secret": "whsec_5b69ff6a12af94c6e3901a061180ca73"
}
The signing secret is only returned once. Store it securely (e.g. in an environment variable). If you lose it, delete the webhook and create a new one.
See Webhooks for the full API reference.

Step 2: Check experiment capacity

Before showing the prompt, verify that the experiment can accept interviews. This prevents showing a prompt that leads to a closed experiment.
const API_KEY = process.env.USERJOURNEYS_API_KEY;

async function getExperiment(name) {
  const response = await fetch(
    "https://app.userjourneys.ai/api/v1/experiments",
    { headers: { Authorization: `Bearer ${API_KEY}` } }
  );
  const { experiments } = await response.json();
  return experiments.find((e) => e.name === name);
}

const experiment = await getExperiment("Onboarding Feedback");

if (experiment?.accepting_interviews) {
  // Show the prompt to the user
}
Cache this response for a few minutes. Interview capacity doesn’t change often, and caching avoids unnecessary API calls.
See Experiments for the full API reference.

Step 3: Show the prompt and open the interview

On your frontend, show a prompt to users who haven’t completed the interview yet. When they click, open the interview link in a new tab.
// Check your database to see if the user already completed the interview
const user = await getUser(userId);

if (!user.interview_completed && experiment.accepting_interviews) {
  showInterviewPrompt({
    message: "We'd love your feedback — it takes about 10 minutes.",
    // Append ?reference_id= so the webhook tells you who completed it
    link: `${experiment.interview_link}?reference_id=${encodeURIComponent(user.id)}`,
    // Open in a new tab so the user stays in your product
    target: "_blank",
  });
}
The interview_link comes from the experiment object you fetched in Step 2. It looks like https://app.userjourneys.ai/i/xK9mR2pQ. Appending ?reference_id= to the link passes the user’s identity through to the webhook payload. This is how you match a completed interview back to the user who clicked — even if multiple users are interviewing at the same time. You can pass any identifier: a user ID, email, UUID, or whatever your system uses.

Step 4: Handle the webhook

When a user finishes the interview, userjourneys.ai sends a POST request to your webhook URL. Verify the signature, then update your database.
import crypto from "node:crypto";
import express from "express";

const SIGNING_SECRET = process.env.USERJOURNEYS_WEBHOOK_SECRET;

function verifySignature(body, signature, secret) {
  const expected =
    "sha256=" +
    crypto.createHmac("sha256", secret).update(body).digest("hex");
  return crypto.timingSafeEqual(
    Buffer.from(signature),
    Buffer.from(expected)
  );
}

app.post(
  "/webhooks/userjourneys",
  express.raw({ type: "application/json" }),
  async (req, res) => {
    const signature = req.headers["x-webhook-signature"];

    if (!verifySignature(req.body, signature, SIGNING_SECRET)) {
      return res.status(401).send("Invalid signature");
    }

    const { event, data } = JSON.parse(req.body);

    if (event === "interview.completed" && data.reference_id) {
      await db.users.update({
        where: { id: data.reference_id },
        data: { interview_completed: true },
      });
    }

    res.status(200).send("OK");
  }
);
Always verify the signature before processing the webhook. Use crypto.timingSafeEqual (Node.js) or hmac.compare_digest (Python) to prevent timing attacks.

What the payload looks like

{
  "event": "interview.completed",
  "timestamp": "2026-03-11T14:32:00.123Z",
  "data": {
    "session_id": "a1b2c3d4-5678-90ab-cdef-111111111111",
    "experiment_id": "e5f6a7b8-1234-56cd-ef78-222222222222",
    "experiment_name": "Onboarding Feedback",
    "interview_link": "https://app.userjourneys.ai/i/xK9mR2pQ",
    "status": "completed",
    "started_at": "2026-03-11T14:20:00.000Z",
    "completed_at": "2026-03-11T14:30:00.456Z",
    "duration_secs": 580,
    "quality_score": "insightful",
    "language": "en",
    "reference_id": "user_12345",
    "respondent_email": null,
    "respondent_id": null
  }
}
See Webhooks — Event: interview.completed for the full payload reference.

Step 5: Stop showing the prompt

Once your database is updated, the check from Step 3 handles the rest — user.interview_completed is now true, so the prompt won’t appear again. That’s it. The full flow:
  1. User sees the prompt → clicks → interview opens in a new tab
  2. User completes the interview → userjourneys.ai sends a webhook
  3. Your server verifies the signature → marks the user as completed
  4. Next time the user loads the page → no prompt

Testing

Use a tool like webhook.site to inspect webhook payloads during development. Point your webhook URL there, complete a test interview, and verify the payload arrives. Once you’re seeing payloads, switch the URL to your real server and test the full flow end-to-end.