Privy can capture a signup, coupon completion, or tracked order in real time. To send email from Privy with Volanea, connect Privy’s Custom webhook to a small server-side relay, then have that relay call Volanea’s REST API with a protected API key.
There is no native Privy app, marketplace listing, or one-click Volanea connector. The reliable integration is a webhook-to-server pattern: Privy emits a signup event, your endpoint validates and deduplicates it, and Volanea sends the transactional message.
What this Privy-to-Volanea integration does
This setup uses Privy as the event source and Volanea as the email delivery layer.
The concrete Privy trigger is a Signup webhook. Privy’s webhook settings let you create a Custom webhook and choose Signups and/or Orders as triggers. A Signup event fires when a contact signs up, including contacts who complete a coupon flow. The event payload includes the contact’s ID, email address, first and last name, custom fields when collected, and the Privy campaign that captured the signup.
That makes the integration especially useful when you want something more specific than a standard marketing welcome sequence, such as:
- Sending a transactional confirmation from your application domain.
- Sending a wholesale or partnership acknowledgement after a particular popup submission.
- Triggering an internal lead-alert email for a sales or support team.
- Sending an onboarding email from a Volanea template while retaining Privy as the on-site form and list-growth tool.
- Routing a signup into your own logic before deciding whether to send, suppress, enrich, or queue an email.
The path looks like this:
- A visitor submits a Privy display and becomes a signup.
- Privy POSTs the signup payload to a public webhook URL you control.
- Your relay checks the request’s Basic Authentication credentials and validates the payload.
- Your relay builds an idempotency key from the Privy signup identity.
- Your relay calls
POST https://api.volanea.com/v1/send. - Volanea performs the send pipeline, including suppression checks, contact upsert, rendering, tracking instrumentation, and dispatch.
The relay is not an unnecessary complication. It keeps Volanea credentials out of browser-visible code and out of an event-source configuration that cannot safely act as a secret manager for a third-party sending credential.
Why Privy needs a webhook relay instead of a direct API call
Privy has Custom webhooks for outbound Signup and Order events. In the Privy dashboard, go to Settings, choose Webhooks under Integrations, select Add Webhook, then choose Custom. Privy lets you set a GET or POST method, a destination URL, and the event triggers.
That capability is enough to notify your system. It is not the same thing as a native Volanea integration.
A direct Privy-to-Volanea call is the wrong shape for three reasons:
Volanea needs its own authenticated request
Volanea’s sending endpoint uses a secret API key. The request must be made by a trusted server process that can send the appropriate authorization header. Privy’s Custom webhook configuration is designed around a destination URL and trigger selection; its documentation recommends HTTP Basic Authentication embedded in the endpoint URL for protecting the receiving endpoint.
Do not put a Volanea secret key in a public website script, in a frontend environment variable, or in a visible form configuration. Anyone who can obtain that credential can potentially send mail through your account.
The Privy payload is a contact event, not an email-send payload
Privy’s Signup webhook sends a contact-oriented JSON object. Volanea’s send API needs message-specific fields such as from, to, subject, and either html, text, or a stored template ID. A relay is where you deliberately map one data model into the other.
A relay gives you control over retries and duplicates
When an upstream event sender does not receive a timely successful response, it may deliver again. Network failures also create an ambiguity: your relay might have sent the email to Volanea but failed before returning a response to Privy. Without idempotency, a retry can turn one signup into two welcome emails.
Volanea supports the Idempotency-Key header on POST /v1/send. Use it on every webhook-driven send.
The Privy Signup webhook payload you should expect
Privy documents the following example payload for a signup webhook:
{
"id": 999999,
"email": "test@example.com",
"first_name": "Test",
"last_name": "User",
"custom_fields": {
"favorite_color": "blue"
},
"created_at": null,
"updated_at": null,
"campaign": {
"id": 1,
"title": "Newsletter signup"
}
}
The fields are useful for different decisions in your relay:
| Privy field | Use in the Volanea workflow |
|---|---|
id | Stable identifier for your idempotency key and logs. |
email | The recipient address for Volanea’s to field. |
first_name | A personalization variable and greeting fallback. |
last_name | Optional personalization or CRM handoff data. |
custom_fields | Values collected on the form, such as interest, source, store location, or product preference. |
campaign.id | Helps make an idempotency key unique per signup source and supports campaign-specific message logic. |
campaign.title | Useful for logging and conditional routing, but avoid treating a human-readable title as a stable ID. |
created_at | Optional diagnostic data. Do not rely on it being populated in every webhook payload. |
Do not assume that every form collects every field. custom_fields can be null, and first or last name may be absent when the display only asks for an email address. Your relay should tolerate missing optional data and reject only fields required to send safely, especially a valid recipient email.
A signup webhook does not itself prove marketing consent for every type of email you might send. Design the message around the actual signup experience and your consent policy. Transactional confirmations and marketing promotions are different categories of email with different legal and operational requirements.
Set up the Privy Custom webhook
Create the event source before deploying the relay, but do not enable it until your public endpoint is ready.
Configure the Signup trigger
In Privy:
- Open Settings in the lower-left area of the dashboard.
- Under Integrations, open Webhooks.
- Select Add Webhook.
- Choose Custom.
- Set the method to POST.
- Enter your publicly accessible relay URL.
- Enable the Signups trigger.
- Save the webhook.
- Use Privy’s Test control after your endpoint is live.
Privy requires a publicly accessible endpoint that resolves to a public IP address. A local URL such as http://localhost:3000 or a URL resolving to 127.0.0.1 will not work from Privy’s servers.
Protect the Privy-facing endpoint with Basic Authentication
Privy recommends HTTP Basic Authentication for a webhook endpoint, using a URL in this pattern:
https://username:password@example.com/webhooks/privy/signup
For example, your configured Privy destination might be:
https://privy-webhook:replace-with-a-long-random-secret@hooks.example.com/webhooks/privy/signup
This credential protects the incoming Privy-to-relay hop. It is not the Volanea API key.
Keep this URL private because it contains the Basic Authentication password. Restrict dashboard access, rotate the password if it is exposed, and avoid pasting it into ticketing systems, screenshots, browser-visible JavaScript, or shared documents.
Send the mapped event to Volanea with a server-side relay
The following Express example accepts a Privy Signup webhook, validates HTTP Basic Authentication, maps the documented Privy fields, and sends a Volanea email. It also derives an idempotency key from the Privy contact ID and campaign ID so the same event does not create repeat sends during the idempotency window.
Before running it, create a verified sending domain in Volanea and configure these server-side environment variables:
VOLANEA_API_KEY=sk_live_replace_me
VOLANEA_FROM=hello@example.com
PRIVY_WEBHOOK_USER=privy-webhook
PRIVY_WEBHOOK_PASSWORD=replace-with-a-long-random-secret
import express from "express";
import crypto from "node:crypto";
const app = express();
app.use(express.json({ limit: "256kb" }));
const {
VOLANEA_API_KEY,
VOLANEA_FROM,
PRIVY_WEBHOOK_USER,
PRIVY_WEBHOOK_PASSWORD,
} = process.env;
function unauthorized(res) {
res.set("WWW-Authenticate", 'Basic realm="Privy webhook"');
return res.status(401).json({ error: "Unauthorized" });
}
function validBasicAuth(req) {
const header = req.get("authorization") || "";
if (!header.startsWith("Basic ")) return false;
const decoded = Buffer.from(header.slice(6), "base64").toString("utf8");
const separator = decoded.indexOf(":");
if (separator === -1) return false;
const username = decoded.slice(0, separator);
const password = decoded.slice(separator + 1);
return (
crypto.timingSafeEqual(Buffer.from(username), Buffer.from(PRIVY_WEBHOOK_USER)) &&
crypto.timingSafeEqual(Buffer.from(password), Buffer.from(PRIVY_WEBHOOK_PASSWORD))
);
}
function escapeHtml(value = "") {
return String(value)
.replaceAll("&", "&")
.replaceAll("<", "<")
.replaceAll(">", ">")
.replaceAll('"', """)
.replaceAll("'", "'");
}
app.post("/webhooks/privy/signup", async (req, res) => {
if (!validBasicAuth(req)) return unauthorized(res);
const signup = req.body;
const email = typeof signup.email === "string" ? signup.email.trim().toLowerCase() : "";
if (!email || !email.includes("@")) {
return res.status(400).json({ error: "Privy signup payload has no valid email" });
}
// Privy documents custom_fields as an object or null.
const customFields = signup.custom_fields && typeof signup.custom_fields === "object"
? signup.custom_fields
: {};
const firstName = typeof signup.first_name === "string" && signup.first_name.trim()
? signup.first_name.trim()
: "there";
const campaignId = signup.campaign?.id ?? "unknown-campaign";
const privyContactId = signup.id ?? email;
// Stable per Privy contact + signup source. Do not use a random UUID here.
const idempotencyKey = `privy-signup:${privyContactId}:campaign:${campaignId}`;
const payload = {
from: VOLANEA_FROM,
to: email,
subject: "Thanks for signing up",
html: `
<p>Hi ${escapeHtml(firstName)},</p>
<p>Thanks for signing up. We’re glad you’re here.</p>
${customFields.favorite_color
? `<p>We noted that your favorite color is ${escapeHtml(customFields.favorite_color)}.</p>`
: ""}
`,
text: `Hi ${firstName},\n\nThanks for signing up. We’re glad you’re here.`,
};
const volaneaResponse = await fetch("https://api.volanea.com/v1/send", {
method: "POST",
headers: {
"Authorization": `Bearer ${VOLANEA_API_KEY}`,
"Content-Type": "application/json",
"Idempotency-Key": idempotencyKey,
},
body: JSON.stringify(payload),
});
const responseBody = await volaneaResponse.text();
if (!volaneaResponse.ok) {
console.error("Volanea send failed", {
status: volaneaResponse.status,
privyContactId,
campaignId,
responseBody,
});
// A non-2xx response tells Privy this delivery was not accepted.
return res.status(502).json({ error: "Volanea send was not accepted" });
}
console.info("Privy signup sent through Volanea", {
privyContactId,
campaignId,
email,
});
return res.status(200).json({ ok: true });
});
app.listen(3000, () => {
console.log("Listening on port 3000");
});
The important field mapping is explicit:
Privy signup.email -> Volanea to
Your verified sender domain -> Volanea from
Privy signup.first_name -> greeting and personalization
Privy custom_fields.* -> optional email content or template variables
Privy signup.id -> Idempotency-Key input
Privy campaign.id -> Idempotency-Key input and routing context
This is a direct server-to-server REST API integration. For additional request fields, templates, scheduling, delivery events, and error handling, use the Volanea API reference and setup guides.
Use a Volanea template when content belongs outside code
Inline HTML is appropriate for a compact confirmation or a simple internal notification. A template is a better fit when marketers or developers need reusable branded content, localization, subject-line changes, or consistent layout across several triggers.
Volanea supports sends using templateId and variables. In that approach, your relay still handles authentication, validation, and idempotency, but the outgoing body becomes smaller and easier to maintain.
Replace the inline subject, html, and text fields with a stored template call like this:
const payload = {
from: VOLANEA_FROM,
to: email,
templateId: "tpl_privy_signup_welcome",
variables: {
firstName,
privyContactId: String(privyContactId),
campaignName: signup.campaign?.title || "Signup",
favoriteColor: customFields.favorite_color || "",
},
};
Keep the idempotency key exactly as deliberate as it was in the inline-content example. A template does not solve duplicate delivery by itself.
There is also a design decision hidden in the template approach: should every Privy signup receive the same email, or should the originating display control the message? Use campaign.id for the rule, not campaign.title. IDs are less likely to change when someone renames a popup from “Newsletter signup” to “Join the list.”
For example, you can maintain an allowlist in code:
const templatesByCampaign = {
"101": "tpl_vip_waitlist",
"202": "tpl_wholesale_interest",
"303": "tpl_general_newsletter_confirmation",
};
const templateId = templatesByCampaign[String(campaignId)];
if (!templateId) {
return res.status(200).json({ ok: true, skipped: "unmapped_campaign" });
}
Returning a successful response for an intentionally skipped event prevents a harmless configuration mismatch from becoming a repeated webhook delivery. Log skipped events so you can spot a new Privy display that needs routing.
Where the Volanea API key belongs
The Volanea API key belongs only in the environment or secret store of the server that runs the relay.
It does not belong in:
- Your storefront’s JavaScript bundle.
- A Privy display’s custom client-side code.
- A browser-accessible
.envfile such asNEXT_PUBLIC_*orVITE_*. - A query string.
- The Privy Custom webhook URL.
- A shared Zap step, screenshot, support ticket, or repository commit.
Privy holds the destination URL for the webhook. If you use Privy’s documented Basic Authentication pattern, Privy also holds the inbound webhook username and password as part of that URL. That credential should only authenticate Privy to your relay.
Your relay holds VOLANEA_API_KEY in a server-side secret manager or environment variable and adds it as the Authorization: Bearer header only when calling https://api.volanea.com/v1/send.
This separation matters because the two secrets serve different purposes:
| Secret | Who uses it | What it protects |
|---|---|---|
| Privy webhook Basic Auth password | Privy and your relay | Prevents arbitrary callers from using your incoming webhook route. |
| Volanea API key | Your relay and Volanea | Authorizes outbound email operations in your Volanea account. |
Use a dedicated Volanea key for this integration where your account’s key-management controls permit it. Rotate it if it is exposed, update the relay’s secret, and verify that the old key no longer works. Also verify your sender domain before testing; Volanea requires the sending domain to be verified before email can be sent from it.
Prevent duplicate sends with idempotency
The most important operational safeguard in this integration is the Idempotency-Key header.
Your webhook endpoint and Privy are separate systems connected by a network. That means you cannot safely infer “one webhook request equals exactly one email.” An event can be re-delivered after a timeout or failed response. Your own process can also crash after Volanea accepts the send but before the relay replies with HTTP 200.
The stable key in the example is:
privy-signup:{contact-id}:campaign:{campaign-id}
This key says: “A given Privy contact joining through a given Privy campaign should create this welcome email once.” It is better than a random UUID because a random value changes on each attempt and therefore cannot identify retries as the same operation.
Pick the correct business event
Idempotency is not just an API implementation detail. It expresses your email policy.
Use a key based on contact ID and campaign ID when one contact should receive one message per display signup. Use only contact ID when the message should be sent once no matter which display the person submits. Add an order ID for an order-triggered receipt. Add a form submission ID if you genuinely want repeat submissions to generate separate messages.
Examples:
- One welcome email ever:
privy-welcome:{contact-id} - One email for each display signup:
privy-signup:{contact-id}:{campaign-id} - One order confirmation:
privy-order:{order-id} - One sales alert per signup:
privy-lead-alert:{contact-id}:{campaign-id}
Do not use created_at as the only key component. Privy’s example Signup payload shows that timestamp fields can be null, and timestamps are poor substitutes for a stable event identity.
Keep an application-level audit record too
Volanea’s idempotency header protects safe API retries. For stronger long-term control, keep a database record or queue job keyed by the same business identifier. Store at least:
- Privy contact ID.
- Privy campaign ID.
- Recipient address.
- Idempotency key.
- Volanea response or message identifier.
- Attempt count.
- Last error and timestamp.
That record lets you answer practical questions such as “Did this subscriber receive the welcome email?” and “Was this failure a Privy delivery issue, a relay issue, or a Volanea rejection?”
When this breaks: Privy webhook and Volanea failure modes
Every integration fails eventually. Design the relay so failures are observable, safe to retry, and clear enough to diagnose.
Privy delivery is repeated and the customer receives duplicate email
Likely cause: The webhook was delivered more than once after a timeout, an unavailable relay, or a non-success response. A duplicate can also occur if your own retry logic makes a new Volanea request with a new idempotency key.
Fix: Derive Idempotency-Key from stable Privy fields such as id and campaign.id. Reuse the same key every time the same business event is processed. Do not generate a random UUID in the webhook handler.
Check: Search your logs for the Privy contact ID, campaign ID, and idempotency key. If the same key appears more than once, that is expected retry behavior; the outcome should be one logical send rather than multiple messages.
Privy times out waiting for the webhook response
Likely cause: Your handler does too much work before responding: a slow database query, template lookup, recipient enrichment, or a delayed API call. Privy’s webhook documentation requires your endpoint to reply with HTTP 200 to acknowledge receipt.
Fix: In production, accept the request quickly, persist a durable job keyed by your idempotency key, return HTTP 200 after the job is safely recorded, and have a worker call Volanea. That architecture reduces timeout pressure without sacrificing reliable delivery.
Important tradeoff: Do not return HTTP 200 before the event is durably queued or recorded. If you acknowledge first and then the process fails, Privy considers the event received while no email is sent.
A signup arrives with missing personalization fields
Likely cause: The Privy display only collected an email address, or custom_fields is null. Privy documents custom fields as optional and single-level when present.
Fix: Treat email as required and use safe defaults for optional fields. In the sample relay, a missing first name becomes “there,” while absent custom fields simply omit the optional sentence.
Check: Compare the Privy display’s actual form fields with the fields your email template expects. Never render raw form values into HTML without escaping them.
A feature is unavailable on the Privy account
Likely cause: Account entitlements can affect access to Privy capabilities. Privy explicitly documents that API access is available to paid accounts and recommends confirming access with Support when valid credentials fail. Privy’s public webhook article documents the webhook configuration flow, but your own account is the source of truth for whether the Webhooks option is available.
Fix: Confirm that Settings → Integrations → Webhooks is available in the account. If you are relying on Privy’s API elsewhere in the workflow, confirm API access and the required account permissions separately.
Do not work around this by exposing the Volanea API key in a frontend form. If outbound webhooks are unavailable for your account, use a supported middleware route or have your application generate the email event directly from the same backend that receives the signup data.
Volanea returns 401 or 403
Likely cause: A missing, incorrect, revoked, or rotated API key can cause authorization failures. A 403 can also occur when the from address uses a domain that has not been verified for sending.
Fix: Confirm the server environment contains the intended Volanea secret key and that VOLANEA_FROM belongs to a verified sending domain. Do not log the full key while diagnosing the problem.
Volanea accepts the request but email does not reach the inbox
Likely cause: Acceptance by an API is not the same thing as inbox placement. Suppression status, recipient reputation, domain authentication, recipient mailbox policies, bounces, complaints, and content can affect the result.
Fix: Review the send record and delivery events in Volanea, verify the sender domain’s authentication, and ensure the recipient is not suppressed or unsubscribed where applicable. Treat a marketing welcome message and a required transactional notice according to their correct consent and suppression rules.
Testing the integration before you publish a Privy display
Test the complete path in a controlled order.
- Verify the sending domain in Volanea.
- Deploy the relay to a public HTTPS URL.
- Set
VOLANEA_API_KEY,VOLANEA_FROM, and the webhook Basic Auth credentials in the server’s secret store. - Configure the Privy Custom webhook with the POST method and Signups trigger.
- Use Privy’s webhook Test action.
- Confirm the relay logs a request and passes Basic Authentication.
- Confirm Volanea records the send request.
- Check the test recipient inbox, spam folder, and Volanea delivery information.
- Submit the real Privy display with a test address.
- Repeat the Privy test or replay the same event in a safe environment to confirm the idempotency behavior.
Use a dedicated test recipient rather than a customer address. If your implementation has campaign-specific routing, test every relevant Privy display because custom-field availability and campaign IDs can differ between forms.
A useful structured log entry might look like this:
{
"event": "privy_signup_to_volanea",
"privyContactId": 999999,
"privyCampaignId": 1,
"recipient": "test@example.com",
"idempotencyKey": "privy-signup:999999:campaign:1",
"status": "accepted"
}
Do not log full request bodies indefinitely. Signup payloads can contain personal data and custom fields that should not be copied into broad-access logs.
Direct webhook relay versus Zapier or Make
A small server-side relay is the best route when you need strong key control, deterministic idempotency, custom HTML or templates, and complete observability.
Zapier or Make can be useful if your team cannot deploy code, but the security and duplicate-handling requirements do not disappear. The middleware platform still needs to hold the Volanea key as a secret connection value, map the Privy fields correctly, and use a stable idempotency key for the Volanea request.
Choose a relay when you need:
- Basic Authentication validation on the inbound request.
- Per-campaign routing logic.
- A database-backed audit trail.
- Queueing for fast webhook acknowledgement.
- Full control over retry behavior.
- A clean separation between form collection, business logic, and email delivery.
Choose no-code middleware only when its available trigger and webhook capabilities match your Privy account, its secret storage is acceptable to your organization, and you can still preserve a stable event identifier through to Volanea.
Operational recommendations for production
Once the basic integration works, improve it before it becomes a high-volume path.
Put email sends behind a durable queue
For a low-volume proof of concept, calling Volanea directly inside the webhook request is understandable. For production, accept and persist the event first, then let a worker send the email.
This gives you controlled retries, lower webhook latency, retry visibility, and a place to apply rate limits or business rules. It also makes your delivery behavior less sensitive to short-lived outbound API problems.
Separate lead emails from customer emails
A Privy Signup event can produce more than one useful action. For example, send the visitor a confirmation and send the sales team an internal notification. Give those sends different idempotency keys:
privy-customer-welcome:{contact-id}:{campaign-id}
privy-sales-alert:{contact-id}:{campaign-id}
Otherwise, one send can incorrectly suppress the other because both actions would look like the same operation.
Keep message purpose clear
Do not use a form signup webhook to bypass consent expectations. The fact that a person submitted an email address through a Privy display does not make every follow-up message appropriate. Use the event for the message the form promised, and keep transactional, operational, and promotional mail streams intentionally designed.
Review sending costs and volume before launch
Webhook-triggered sends can grow faster than expected when a high-traffic display is published. Estimate your maximum signup rate, account for retries and internal notifications, and review transactional email pricing before moving a high-volume form into production.
Conclusion
The dependable way to send email from Privy with Volanea is not a marketplace install. It is a secure, explicit integration built around Privy’s Custom Signup webhook and a server-side relay.
Use Privy’s documented Signup payload as the input, validate it at a public endpoint protected with Basic Authentication, keep the Volanea API key only in server-side secrets, map email to the recipient, and send through POST /v1/send. Most importantly, derive a stable Idempotency-Key from Privy’s contact and campaign identifiers so retries do not become duplicate customer emails.
That design gives you the flexibility of Privy’s displays and signup capture with Volanea’s REST sending, deliverability controls, API-based templates, and delivery visibility.
FAQ
Does Privy have a native Volanea integration?
No. Privy does not provide a native Volanea app, marketplace listing, or one-click connector. Use Privy’s Custom webhook to notify a server-side relay, then call Volanea from that relay.
What Privy event triggers the email in this guide?
This guide uses Privy’s Signups webhook trigger. Privy can also send webhook events for Orders, but the payload and idempotency strategy should be adjusted for order-specific sends such as receipts or internal order alerts.
Can I send directly from Privy to the Volanea API URL?
Do not rely on that approach. Privy’s Custom webhooks are designed to send event payloads to your endpoint, while Volanea needs an authenticated email-send request with a mapped payload. A relay keeps the Volanea API key private and gives you validation, routing, and duplicate protection.
Where should I store the Volanea API key?
Store it in the environment variables or secret manager of your server-side relay. Never put it in Privy form code, browser JavaScript, a public environment variable, or the Privy webhook URL.
How do I stop a Privy webhook retry from sending another email?
Set a stable Idempotency-Key on the Volanea POST /v1/send call. Build it from durable event identifiers, such as Privy’s signup id and campaign.id, and reuse the same key whenever processing the same signup event.