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
POSTwith a JSON body. - Access to the Rose backoffice Integrations page for your site.
Set it up¶
- In the Rose backoffice, open Integrations and find the Scheduled export webhook card (under Webhooks).
- 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. |
- 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. |