10 min read

Migrate from Delighted to Revuloop

End-to-end guide for moving your NPS, CSAT, CES, and eNPS programs from Delighted to Revuloop. Includes the export script, the in-app wizard, and the post-migration cutover checklist.

Qualtrics has discontinued Delighted, and customer data was permanently deleted at shutdown. If you saved your Delighted export (Projects, Responses, Recipients, and embed Snippets), this guide walks you end-to-end through moving your CX program over to Revuloop.

The Revuloop in-product wizard at Settings → Import Data handles the heavy lifting (idempotent imports, historical timestamps preserved, contact upserts). This article covers everything around the wizard: what to gather first, how to pull the data out of Delighted, and what to do after the import finishes.

Coming from a different platform? The same wizard imports a CSV export from SurveyMonkey, Typeform, Google Forms, Qualtrics, or any other tool. See the article "Import Responses from Any Platform (CSV)" in this help center.
Already have your export? If you saved your Delighted data before the platform shut down, skip ahead to Step 3: Run the import wizard and upload your saved JSON. Steps 1–2 below cover pulling data out of Delighted and only apply if you still have account access.

Before you start

  1. Confirm admin access to your Delighted account: you'll need the API key, which lives in Settings → API.
  2. List every Project you want to migrate. Each Delighted Project (NPS, CSAT, CES, 5-star, smileys, thumbs, eNPS, PMF) becomes one Revuloop survey. The wizard handles one Project per run, so plan to repeat the wizard for each one.
  3. Decide on suppress-webhooks. During backfill we recommend suppressing webhook events so your downstream subscribers (Salesforce, HubSpot, Slack, etc.) aren't flooded with months of historical responses. The wizard defaults to suppressed.
  4. Have a Revuloop account ready. Sign up at /auth/signup, create or join an organization, and confirm you have the Owner or Admin role; the import wizard requires it.

Step 1: Get your Delighted API key

  1. Sign in to Delighted at https://app.delighted.com.
  2. Open Settings → API (direct URL: https://app.delighted.com/account/api).
  3. Copy the API key. It's a 32-character string. Keep this somewhere safe for the next step; it grants full read access to your Delighted data.

Step 2: Export your responses from Delighted

Delighted's REST API at `https://api.delighted.com/v1/survey_responses.json` returns paginated responses. Default page size is 20; maximum is 100. You need to walk all pages and concatenate them into a single JSON file.

Replace `DELIGHTED_API_KEY` with the key from Step 1, then run:

API_KEY="DELIGHTED_API_KEY"
page=1
while :; do
  out="page-$(printf '%03d' $page).json"
  curl -sS -u "$API_KEY:" \
    "https://api.delighted.com/v1/survey_responses.json?per_page=100&page=$page&expand=person,survey_response_notes" \
    > "$out"
  count=$(jq 'length' "$out")
  echo "page $page: $count rows"
  [ "$count" -lt 100 ] && break
  page=$((page + 1))
done

jq -s 'add' page-*.json > delighted-export.json
echo "Total: $(jq 'length' delighted-export.json) responses → delighted-export.json"

That loops until a page comes back with fewer than 100 rows (the natural end-of-data signal), then `jq -s 'add'` concatenates every page into one array.

Option B: Node script

If you don't have `jq` and `curl` handy, drop this into a `fetch-delighted.mjs` file and run `node fetch-delighted.mjs`:

import https from "node:https";
import fs from "node:fs";

const API_KEY = process.env.DELIGHTED_API_KEY;
if (!API_KEY) throw new Error("Set DELIGHTED_API_KEY in your environment");

const all = [];
for (let page = 1; ; page++) {
  const data = await new Promise((resolve, reject) => {
    https.get(
      {
        hostname: "api.delighted.com",
        path: `/v1/survey_responses.json?per_page=100&page=${page}&expand=person,survey_response_notes`,
        headers: { Authorization: `Basic ${Buffer.from(API_KEY + ":").toString("base64")}` },
      },
      (res) => {
        let buf = "";
        res.on("data", (c) => (buf += c));
        res.on("end", () => resolve(JSON.parse(buf)));
        res.on("error", reject);
      }
    );
  });
  console.log(`page ${page}: ${data.length} rows`);
  all.push(...data);
  if (data.length < 100) break;
}

fs.writeFileSync("delighted-export.json", JSON.stringify(all, null, 2));
console.log(`Total: ${all.length} responses → delighted-export.json`);

What the export contains

Each row in `delighted-export.json` includes:

  • `id`: the Delighted response ID (used for idempotency; safe to re-import)
  • `score`: the primary metric value
  • `comment`: the open-ended follow-up
  • `created_at` / `updated_at`: original timestamps (Revuloop preserves these)
  • `person.email`: respondent email (optional)
  • `person_properties`: your custom fields (plan, tier, etc.)
  • `notes` and `tags`: Delighted's manual tagging artifacts
  • `additional_answers`: Additional Questions data

All of this carries over into Revuloop. Personal properties, tags, and notes are stored on each response under `sourceDetails.extras` (queryable but not yet rendered in the dashboard; see "What carries over" below).

Multi-project accounts

If your Delighted account has multiple Projects (e.g. an NPS Project and a CSAT Project), the `/v1/survey_responses.json` endpoint returns responses across all Projects together. Split them by `survey_type` before uploading:

jq 'map(select(.score | type == "number" and . >= 0 and . <= 10))' delighted-export.json > delighted-nps.json
jq 'map(select(.score | type == "number" and . >= 1 and . <= 5))' delighted-export.json > delighted-csat.json

(Adjust the filters to match the score ranges of your specific Projects.) Then upload each split file separately, picking the matching survey type in the wizard.

Step 3: Run the import wizard

  1. Sign in to Revuloop and open Settings → Import Data (URL: /org/[your-org-slug]/settings/import).
  2. Click the Delighted card.
  3. Drag-drop `delighted-export.json` (or any of the split files) into the upload zone. The wizard parses it client-side and shows the response count.
  4. On the next screen:
    • New survey title: defaults to the file name; edit if you want.
    • Survey type in Delighted: pick NPS / CSAT / CES / eNPS / 5-star / Smileys / Thumbs / PMF. The wizard infers a sensible default from the score ranges, but verify it.
    • Primary question text *(optional)*: override the default question text if your Delighted Project used custom phrasing.
    • Workspace: pick which Revuloop workspace the new survey lives in.
    • Suppress webhooks during import *(recommended, on by default)*: keeps your subscribers from being flooded with months of backfilled `RESPONSE_SUBMITTED` events.
    • Import respondent emails as contacts *(on by default)*: upserts a Contact row per unique email, idempotent on `(userId, email)`.
  5. Click Start import.

What happens next

  • Up to 100 responses → the wizard runs the import synchronously and shows progress per batch. Takes a few seconds.
  • More than 100 responses → the wizard uploads the payload to a background `MigrationJob`, then polls progress every 2 seconds. You can safely close the tab; the import runs server-side and resumes through retries automatically.

The wizard auto-batches uploads in 500-row chunks (well under Vercel's request size cap) and idempotency means a half-completed import can be re-uploaded with no duplicates.

Step 4: Post-migration cutover checklist

The import handles your historical data. Live traffic still needs to be re-pointed.

Swap the Delighted Web Snippet for the Revuloop Embed SDK

If you were using Delighted's Web Snippet to collect in-product feedback, you'll need to install Revuloop's Embed SDK in its place:

  1. Open the migrated survey in Revuloop.
  2. Go to Distribute → Embed.
  3. Pick the embed mode (slider, popup, inline, full-screen).
  4. Copy the install snippet and replace the Delighted snippet on each site.

The Embed SDK is documented at /developers/docs/embed.

Re-create your webhooks

If you had Delighted webhooks firing into Salesforce, HubSpot, Slack, or your own backend, set up the equivalent Revuloop webhooks:

  1. Settings → Webhooks → Create webhook.
  2. Point at the same downstream URL.
  3. Subscribe to `RESPONSE_SUBMITTED`.
  4. Verify the HMAC signature on your downstream receiver; Revuloop signs every delivery (see the Webhooks article).

The payload shape differs from Delighted's, so your downstream parsing code will need a one-time update. The webhook documentation includes a sample payload.

Generate an API key for backend integrations

If your team has scripts or backend services that hit the Delighted API, you'll need a Revuloop API key:

  1. Settings → API Keys → Create new key.
  2. Pick LIVE environment.
  3. Give it the scopes your scripts need (typically `surveys:read`, `responses:read`, `responses:write`).
  4. Copy the key immediately; it's only shown once.

API docs live at /developers/docs.

Reconfigure CRM integrations

Delighted pushed responses into Salesforce, HubSpot, Zendesk, and Intercom. Revuloop has equivalent integrations under Settings → Integrations. Enable each one and point it at the migrated survey.

Set up recurring sends (if you used Autopilot)

Delighted's Autopilot sent NPS surveys on a 3/6/12-month recurring cadence. The Revuloop equivalent is Scheduled Sends under the survey's Distribute → Email tab. Configure the cadence and recipient list there.

Confirm everything came across

Once you've verified the migrated survey looks correct (open a few responses, check that timestamps match the originals), you're done. With Delighted discontinued, Revuloop is now your system of record. Before you move on, make sure every Project you cared about has been imported.

What carries over and what doesn't

Preserved end-to-end

  • Response counts and completion timestamps: `created_at` becomes `startedAt`, `updated_at` becomes `completedAt`.
  • Score and comment: mapped to the primary question + a `long_text` follow-up.
  • Additional Questions: mapped to the appropriate Revuloop question type (free response → `short_text`, single select → `multiple_choice`, multi select → `checkbox`, rating → `rating`, scale → `scale`).
  • Respondent emails: upserted as Contact rows if you left the toggle on.
  • Delighted response IDs: stored under `Response.sourceDetails.sourceId` for idempotent reruns.
  • `person_properties`, `tags`, `notes`: stashed under `Response.sourceDetails.extras` (queryable via the API; surfacing them in the dashboard UI is on the roadmap).

Not preserved automatically (manual work in Step 4)

  • Web Snippet installs: install the Revuloop Embed SDK in their place.
  • Webhook subscriptions: re-create under Settings → Webhooks.
  • CRM integration configs: re-enable under Settings → Integrations.
  • Autopilot cadence: recreate as a Scheduled Send.
  • Throttling rules: Delighted's "1 survey per recipient per 30 days" is a setting under the survey's distribution tab in Revuloop.
  • Custom branding: re-upload your logo and brand colors under Settings → Branding.

Troubleshooting

The wizard says "No Delighted responses found in this file." The file you uploaded didn't match any expected shape. The wizard accepts a bare response array (`[{ ... }, { ... }]`), an API-style envelope (`{ "responses": [...] }`), or an admin export wrapper (`{ "data": [...] }`). Check that your file is one of those.

Some responses are skipped. That's expected if you re-upload the same file: every Delighted response ID is checked against `sourceDetails.sourceId` and already-imported rows are skipped, not re-inserted. The success screen shows the count.

The background job is stuck in `PENDING`. Inngest may be processing other jobs. `PENDING` jobs typically start within a minute. If it stays in `PENDING` for more than 10 minutes, contact support; there may be an issue with the Inngest worker.

The job ended in `FAILED`. Inngest retries the import 3 times before giving up. The error message is shown on the wizard. Common causes: the survey was deleted mid-import, the questionMap doesn't match the survey's questions (rare, and only happens if someone edits the survey while the import is queued), or a database connection issue. Re-running the wizard with the same file is safe (idempotency layer skips already-imported rows).

My contacts didn't get imported. Check that the Import respondent emails as contacts toggle was on. If yes, look at `/org/[slug]/contacts`. Contacts are upserted per `(userId, email)`, so emails you already had won't show as "new." The wizard's success screen splits the count into "added" vs "already in list."

The imported survey is in `DRAFT` status. That's intentional: review the survey first, then click Launch when you're ready to start collecting new responses. Existing imported responses are visible regardless of the survey's status.

If you hit a blocker mid-migration

Mention "Delighted migration: urgent" in your subject line and we'll prioritize it ahead of the normal queue.

Still need help?

Our support team typically responds within 24 hours