Skip to content

Scheduled Export Webhook

Receive your Conversations and Accounts exports on your own endpoint every day or every week, instead of downloading the CSV files from the backoffice by hand. Wire them into your data warehouse, CRM, n8n, Zapier, or any HTTPS endpoint.

Each delivery carries the same rows as the backoffice CSV export for the same period, as JSON.

This is self-serve: you set it up yourself on the Integrations page, no account manager needed.

How it works

Every morning, Rose checks whether a new period is complete and, if so, sends it to your webhook URL:

Frequency What is sent When
Every day The previous day, midnight to midnight Every morning
Every Monday The previous week, Monday 00:00 to the following Monday 00:00 Monday morning

Days start at midnight in your timezone (Europe/Paris unless your account manager set another one). A period always covers whole days: two consecutive deliveries never overlap and never leave a gap.

Rose sends one POST per dataset and period: one for conversations, one for accounts. A period with no activity is still sent, with an empty rows list, so you can tell "nothing happened" from "nothing arrived".

If your endpoint is down or rejects the request, Rose retries a few times, then tries the same period again the next morning, with the same event_id. Later periods wait until the earlier one is accepted, so they always arrive in order.

What you need

  • An HTTPS endpoint that accepts a POST with a JSON body.
  • Access to the Rose backoffice Integrations page for your site.

Set it up

  1. In the Rose backoffice, open Integrations and find the Scheduled export webhook card (under Webhooks).
  2. Turn it on, then click Configure and fill in:
Setting What to enter
Webhook URL The HTTPS address that receives the exports.
Secret key (optional) A secret you choose so your endpoint can confirm the request really came from Rose. Stored encrypted. See Verifying the signature.
Frequency Every day or Every Monday.
What to send Conversations, Accounts, or both.
  1. Click Save, then Send test request to check that your endpoint is reachable. The test request has "event_type": "test"; ignore it in your endpoint. The first real delivery arrives the next morning (or next Monday) with the last complete period.

The payload

{
  "event_type": "conversations_export",
  "event_id": "6c201f...6e8a1",
  "site_name": "yourdomain.com",
  "period_start": "2026-10-05T00:00:00+02:00",
  "period_end": "2026-10-12T00:00:00+02:00",
  "timezone": "Europe/Paris",
  "part": 1,
  "parts": 1,
  "rows": [
    {
      "started_at": "2026-10-06T09:12:44.120000+00:00",
      "account_name": "Acme Inc",
      "visitor_name": null,
      "visitor_email": "jane@acme.com",
      "visitor_id": "0f3c...",
      "rose_visitor_id": "0191f2a4-...",
      "session_id": "a81e...",
      "page_url": "https://yourdomain.com/pricing?utm_source=google",
      "utm_source": "google",
      "utm_medium": "cpc",
      "utm_campaign": "fall",
      "conversation_language": "fr",
      "total_turns": 4,
      "topics": "Pricing, Integrations",
      "keywords": "hubspot, price",
      "email_asked": true,
      "email_captured": true,
      "demo_proposed": true,
      "cta_clicked": false,
      "demo_booked": false,
      "converted": true,
      "helpfulness": 4,
      "resolution": 5,
      "transcript": "User: ...\nAssistant: ..."
    }
  ]
}
Field Description
event_type conversations_export or accounts_export.
event_id Stable id for one site, dataset and period. A request can be repeated: de-duplicate on event_id and part. A period that failed is sent again in full on a later run, from part 1, and its rows can be split differently: when part 1 of an event_id you already have arrives, discard the parts you stored for it and keep the new ones.
site_name Your site domain.
period_start, period_end The period covered, start included, end excluded, with your timezone's offset.
timezone The timezone the days are cut in.
part, parts Large periods (above about 5 MB) are split into several requests, sent in order: part 1 to parts, all with the same event_id. A period is complete once you have every part. Most periods fit in one part.
rows The export rows.

Conversation rows

One row per conversation started in the period, with the columns of the backoffice Conversations CSV export (conversation format): started_at, account_name, visitor_name, visitor_email, visitor_id, rose_visitor_id, session_id, page_url, utm_source, utm_medium, utm_campaign, conversation_language, total_turns, topics, keywords, email_asked, email_captured, demo_proposed, cta_clicked, demo_booked, converted, helpfulness, resolution, transcript.

Production conversations from the widget on your site are included, as in the backoffice default view.

Account rows

One row per company active in the period (a visit or a message), with the columns of the backoffice Accounts CSV export: company_name, company_domain, country, industry, sub_sector, employee_count, turnover, description, last_seen_at, last_message_at, message_count, visitor_count, first_visitor_email, max_intent_score, email_asked, email_captured, demo_proposed, cta_clicked, demo_booked.

Dates are ISO 8601 timestamps in UTC; yes/no columns are true / false. Any value Rose doesn't know is null.

Verifying the signature

If you set a Secret key, Rose adds an X-Webhook-Signature header to every request: a hex-encoded HMAC-SHA256 of the raw request body, keyed with your secret. Recompute it on your side and compare.

const crypto = require('crypto');

function isFromRose(rawBody, signatureHeader, secret) {
  const expected = crypto
    .createHmac('sha256', secret)
    .update(rawBody) // the exact raw bytes, before JSON parsing
    .digest('hex');
  return crypto.timingSafeEqual(
    Buffer.from(expected),
    Buffer.from(signatureHeader)
  );
}

Sign over the raw body bytes exactly as received: parsing and re-serializing the JSON can change the bytes and break the check.

Troubleshooting

Symptom Likely cause / fix
No requests at all Check the card is on, Save was clicked and the URL starts with https://. The first delivery comes the morning after you enable it (the Monday after, for weekly).
The same period arrived twice Your endpoint answered with an error or too slowly, so Rose sent the request again. De-duplicate on event_id and part; when part 1 arrives again, replace the parts you stored for that event_id.
Your endpoint times out Each request must be answered within 30 seconds. Accept the body, answer 200, and process it afterwards.
Row count differs from a CSV export Pick the same whole days in the backoffice export. The webhook uses whole days in your timezone, production widget traffic only.