Integrations
Send every lead where you already work
Google Sheets, HubSpot, Zapier, Make, your own server through signed webhooks, and a leads API. All of them are included on the Scale plan and set up from Contacts → Integrations in the dashboard.
Google Sheets
New leads are added to a spreadsheet Replavia creates in your Google Drive.
HubSpot
New leads become HubSpot contacts, matched on email so nothing is duplicated.
Zapier
Start a Zap from each new lead with a “Webhooks by Zapier” Catch Hook.
Make
Start a scenario from each new lead with Make’s “Custom webhook” trigger.
Your own server
Signed webhooks for every lead event, retried when your endpoint is down.
Leads API
Read your leads with a paginated REST endpoint and a key you control.
What every integration has in common
- Scale plan. Webhooks, the leads API, Google Sheets and HubSpot are part of Scale. If a workspace leaves Scale, the leads API answers
403, Google Sheets and HubSpot syncing stops, and webhooks stop sending lead data. Deletion signals, includingopt_out, are still sent. Leads captured in the meantime are not sent later, and a Scale subscription whose payment is past due counts as having left Scale. - New leads only. Connecting a destination does not copy leads captured before it was connected. Use the CSV export for those.
- No Instagram user ID, anywhere. Every lead is identified by Replavia’s own lead id. The person’s Instagram user ID is never sent to a webhook, the API, a sheet or HubSpot.
- Deletions follow the data. When someone unsends the message that shared their email or phone, or asks for their data to be deleted, Replavia tells each connected destination, as described in each section below.
Google Sheets
Connect a Google account and Replavia creates a spreadsheet for your leads, then adds new ones about every 30 minutes. Replavia can only open files it created itself (Google’s drive.file access), so it cannot read the rest of your Drive.
| Column | What it holds |
|---|---|
| Lead ID | Replavia’s id for the lead. Stays in place even after a deletion. |
| Name | The person’s Instagram username |
| The email they shared, if any | |
| Phone | The phone number they shared, if any |
| Automation | The name of your automation that captured them |
| Status | NEW, CONTACTED, CONVERTED, IGNORED or JUNK |
| Captured (date, UTC) | The day the lead was captured |
The column order is fixed, so formulas and lookups that point at it keep working. When a person unsends a detail, opts out or asks for deletion, their Name, Email and Phone cells are cleared and the Lead ID stays.
HubSpot
Connect your HubSpot account and new leads are created or updated as contacts about every 30 minutes. A lead with an email address is matched on email, so an existing contact is updated rather than duplicated. A lead without one is matched on its Replavia Lead ID. Empty values are never written over a field you already have.
| HubSpot property | Value |
|---|---|
| The email they shared | |
| Phone number | The phone number they shared |
| First name | Their Instagram username |
| Replavia Lead ID | Replavia’s id for the lead (a unique property) |
| Replavia Source | The automation that captured them |
| Replavia Intent | What they were asking about, when Replavia detected it |
| Replavia Status | NEW, CONTACTED, CONVERTED, IGNORED or JUNK |
| Replavia Captured At | The capture date, as YYYY-MM-DD |
Replavia creates the five “Replavia” properties in your account on the first sync. If a person unsends a detail, their email and phone are cleared on the HubSpot contact. If they ask for their data to be deleted, the contact is archived.
Webhooks
A webhook sends each lead event to a URL you choose, as an HTTPS POST with a JSON body. Add up to 10 endpoints per workspace, and each one receives every event.
Endpoint requirements
https://only, on the standard port 443, with a valid certificate (TLS 1.2 or later).- The host must resolve to a public internet address. Private and internal addresses are refused.
- Redirects are not followed, so give the final URL.
- Our sending IP address is not fixed. Verify the signature instead of allow-listing an address.
Verification before any lead is sent
When you add an endpoint, Replavia first sends it a webhook.verify event with no lead data in it. Reply with any 2xx status and the endpoint becomes active. Until it does, no lead is sent to it. Your signing secret (whsec_…) is shown once, when you create the endpoint. If you rotate it, the old secret keeps signing alongside the new one for 24 hours so you can switch over without dropping a delivery.
Events
| Event | When it is sent |
|---|---|
lead.captured | Someone becomes a contact for the first time — usually by triggering one of your automations, or by messaging you through an ice breaker or DM link. Email and phone may still be empty at this point. |
lead.updated | Replavia records something about an existing contact again: they trigger an automation (the same one or another), share an email or phone number (including with the AI assistant), or opt out by replying STOP, which sets subscribed to false (if your workspace can receive lead data at that moment; otherwise see lead.deleted "opt_out"). A STOP from someone who was not yet a contact also arrives as lead.updated, so treat an id you have not seen as a new lead. |
lead.deleted | Carries only the lead id and a reason. "unsend": the person unsent the message that shared their email or phone. The lead still exists, so clear its email and phone (a later lead.updated can follow). "erasure": the person asked for their data to be deleted, so delete the record. "opt_out": the person replied STOP at a time Replavia could not confirm your workspace may receive lead data, for example after leaving the Scale plan. The lead is not deleted, so keep the record and stop marketing to them. |
Changes you make yourself in the dashboard, such as a status, a note or a tag, are not sent as events. Read them with the leads API.
Headers
| Header | Value |
|---|---|
Content-Type | application/json |
User-Agent | Replavia-Webhooks/1.0 (+https://replavia.com) |
X-MDF-Webhook-Id | The event id, the same as the body’s top-level "id". It stays the same on every retry. Use it to ignore repeats. |
X-MDF-Webhook-Event | The event type, for example lead.captured |
X-MDF-Webhook-Timestamp | When this attempt was signed, in Unix seconds |
X-MDF-Webhook-Signature | t=<timestamp>,v1=<signature> — see “Verifying the signature” |
Payload
{
"version": 1,
"event": "lead.captured",
"id": "evt_3xQ9m2KfT7vLp0aZ8nWc4Rj1bYs",
"occurredAt": "2026-09-13T14:02:51.337Z",
"data": {
"id": "cmf1k8z2q0003xyz9abc4def5",
"username": "jane.bakes",
"email": "jane@example.com",
"phone": null,
"source": "Recipe giveaway",
"account": "yourbrand",
"status": "NEW",
"subscribed": true,
"note": null,
"tags": [],
"capturedAt": "2026-09-13T00:00:00.000Z"
}
}occurredAt is the exact moment the change happened. capturedAt is recorded to the day, so its time is always midnight UTC. account is your connected Instagram account, and source is the name of your automation. A lead.deleted event carries no personal data, only the id to match on:
{
"version": 1,
"event": "lead.deleted",
"id": "evt_Vb7Tq1mX0pLs9dK2wZc8Hn4yRa6",
"occurredAt": "2026-09-14T09:15:03.120Z",
"data": {
"id": "cmf1k8z2q0003xyz9abc4def5",
"reason": "unsend"
}
}Verifying the signature
Each request is signed with HMAC-SHA256 using your endpoint’s secret. The signed text is mdf.webhook.v1. followed by the X-MDF-Webhook-Id value, a dot, the timestamp t, a dot, and the raw request body. The signature is base64-encoded. While a secret is being rotated, the header carries two v1= values, so accept the request if either one matches. Always verify against the body bytes exactly as received, because re-serialised JSON will not match. In Node.js:
import { createHmac, timingSafeEqual } from 'node:crypto';
// header = the X-MDF-Webhook-Signature header
// id = the X-MDF-Webhook-Id header
// rawBody = the request body exactly as received (a Buffer, not re-serialised JSON)
// secret = your endpoint's signing secret (whsec_…)
export function verifyReplavia(header, id, rawBody, secret) {
if (!header || !id || !secret) return false;
let ts = null;
const sigs = [];
for (const part of header.split(',')) {
const [k, ...rest] = part.trim().split('=');
const v = rest.join('='); // base64 can end in '='
if (k === 't') ts = Number(v);
else if (k === 'v1' && v) sigs.push(v);
}
if (ts === null || sigs.length === 0) return false;
if (Math.abs(Math.floor(Date.now() / 1000) - ts) > 300) return false; // 5 minutes
const signed = Buffer.concat([Buffer.from(`mdf.webhook.v1.${id}.${ts}.`, 'utf8'), Buffer.from(rawBody)]);
const expected = createHmac('sha256', secret).update(signed).digest();
return sigs.some((s) => {
const got = Buffer.from(s, 'base64');
return got.length === expected.length && timingSafeEqual(got, expected);
});
}Responses, timeouts and retries
Reply with a 2xx within 10 seconds, and do slow work after you have replied. Delivery is at least once, and the order of events is not guaranteed. De-duplicate on X-MDF-Webhook-Id, and use occurredAt to ignore an update older than one you have already applied.
| Your endpoint returns | What Replavia does |
|---|---|
| 2xx | Delivered |
| A timeout or connection error | Retries. A hostname that cannot be resolved at all is not retried. |
| 5xx | Retries |
| 408 or 429 | Retries, and waits as long as a Retry-After header asks (up to 6 hours) |
| 410 Gone | Stops retrying that event. New events are still sent, so delete an endpoint you no longer use. |
| 3xx, or any other 4xx | Stops retrying that event, because it is treated as a configuration problem |
An event is tried up to 8 times, over up to about 20 hours. The waits between attempts grow from seconds to hours, and each one is randomised so a recovering server is not hit all at once. An endpoint that fails 20 times in a row, over at least 6 hours with no successful delivery in the last 24 hours, is switched off.
Zapier and Make
Zapier and Make connect through their own webhook triggers. There is no listed Replavia app in either directory.
- Zapier: create a Zap with the “Webhooks by Zapier” app and the Catch Hook trigger, then copy the URL it gives you. Check that your Zapier plan includes Webhooks by Zapier. Make: add the Webhooks module’s Custom webhook trigger and copy its address.
- In Replavia, open Contacts → Integrations and add that URL as a webhook endpoint.
- Both reply
2xxautomatically, so the verification event activates the endpoint on its own. That first request is the verification event, so map your fields from a reallead.capturedevent once a lead arrives. - Optionally, filter on the
eventfield so each Zap or scenario handles only the events it needs.
Leads API
GET https://app.replavia.com/api/v1/leads returns your leads, newest capture first. Create a key in Contacts → Integrations. It starts with mdf_live_ and is shown once. A workspace can have up to 5 active keys, and you can revoke any of them at any time.
curl "https://app.replavia.com/api/v1/leads?limit=100&status=NEW" \
-H "Authorization: Bearer mdf_live_…"Query parameters
| Parameter | Meaning |
|---|---|
limit | Leads per page. The default is 50 and the maximum is 200. |
cursor | The next_cursor value from the previous page. Pages are cursor-based, so leads arriving while you page do not shift the pages you have not read yet. |
status | Only leads in this status: NEW, CONTACTED, CONVERTED, IGNORED or JUNK |
since | An ISO 8601 timestamp. Only leads that changed at or after it are returned, which suits an incremental sync. A value that is not a valid date is ignored, and every lead is returned. |
Response
{
"data": [
{
"id": "cmf1k8z2q0003xyz9abc4def5",
"username": "jane.bakes",
"email": "jane@example.com",
"phone": null,
"source": "Recipe giveaway",
"account": "yourbrand",
"status": "NEW",
"subscribed": true,
"note": null,
"tags": [],
"capturedAt": "2026-09-13T00:00:00.000Z",
"updatedAt": "2026-09-13T14:02:51.337Z"
}
],
"has_more": true,
"next_cursor": "Y21mMWs4ejJxMDAwM3h5ejlhYmM0ZGVmNQ"
}The fields match the webhook payload, plus updatedAt. When has_more is false, you have reached the last page. Leads whose data has been erased are never returned.
Errors and limits
| Status | Meaning |
|---|---|
| 400 invalid_status | status is not one of the allowed values (the response lists them) |
| 401 unauthorized | The key is missing, wrong or revoked. All three return the same answer. |
| 403 upgrade_required | The workspace is not on the Scale plan |
| 429 rate_limited | Too many requests. Stay under 120 a minute per key, and wait for the Retry-After header (60 seconds) before trying again. |
Questions about an integration? See pricing or all features.
Your next 100 customers are in your comments.
Create your free account, connect Instagram, and Replavia starts replying — and selling — around the clock.
Free forever plan · Cancel anytime
LINK please 🔥
Auto-DM sent · instantdrop the link 👀
Auto-DM sent · instanthow much?? 🙏
Auto-DM sent · instantwhere do I buy this?
Auto-DM sent · instantsend me the link
Auto-DM sent · instanti need this 😍
Auto-DM sent · instantprice?
Auto-DM sent · instanthow to order? 🛒
Auto-DM sent · instantLINK please 🔥
Auto-DM sent · instantdrop the link 👀
Auto-DM sent · instanthow much?? 🙏
Auto-DM sent · instantwhere do I buy this?
Auto-DM sent · instantsend me the link
Auto-DM sent · instanti need this 😍
Auto-DM sent · instantprice?
Auto-DM sent · instanthow to order? 🛒
Auto-DM sent · instant