Send email from Chargebee with Volanea by routing a Chargebee webhook through a secure server-side endpoint, then calling Volanea’s REST API. Chargebee does not provide a native Volanea marketplace app, so the reliable integration pattern is Chargebee → your webhook handler → Volanea.

This guide uses Chargebee’s subscription_created webhook event as the concrete trigger. When Chargebee records a new subscription, it sends an HTTP POST containing an event envelope, customer data, subscription data, and—in this event—invoice data. Your endpoint validates and deduplicates that event, maps the customer email and subscription fields into a transactional message, and sends it through Volanea.

What this integration does—and what it does not do

The goal is simple: a customer starts a subscription in Chargebee, and they receive an email from your verified sending domain. That message might be a welcome email, an onboarding note, a receipt-adjacent confirmation, or a notice that tells the customer what to do next.

The important architectural detail is that Chargebee is the event source, not the email sender. Chargebee webhooks can notify an HTTP endpoint when billing events happen, but a webhook configuration is not a general-purpose secret store or an email-template engine. Volanea performs the email send after your middleware receives the event.

The flow looks like this:

  1. A billing action creates a subscription in Chargebee.
  2. Chargebee records the subscription_created event.
  3. Chargebee sends an application/json HTTP POST to the webhook URL you configured.
  4. Your server verifies the request at the network and HTTP-authentication layers, validates the event, and checks whether its event ID was already processed.
  5. Your server sends one mapped transactional email to POST https://api.volanea.com/v1/send.
  6. Your server returns a 2XX response to Chargebee only after the event is safely handled according to your chosen reliability model.

This is deliberately different from putting an email API key in frontend JavaScript, a hosted checkout page, a browser extension, or a Chargebee custom field. Those locations can expose credentials or make retry handling unpredictable. The Volanea secret key belongs only in a server-side secret manager or environment variable available to your webhook handler.

The Chargebee trigger: subscription_created

For this implementation, the trigger is Chargebee’s subscription_created event. Chargebee documents this event as firing when a new subscription is created. Its event content includes the affected subscription, customer, and invoice resources.

That makes it a useful onboarding trigger because the customer record is part of the same webhook payload. In the normal case, your handler can use content.customer.email as the recipient and content.subscription.id as the reference that ties the email back to the billing event.

Do not assume that subscription_created always means a customer has immediately paid or that their subscription is active in every business model. A subscription can be created in different lifecycle states depending on your checkout, trial, collection, and provisioning design. If your message promises that access is live, choose the event that matches that promise—often subscription_activated rather than subscription_created.

Why event choice matters

A good transactional message describes a fact that is true at the time it is sent. For example:

  • Use subscription_created for “We received your subscription request” or “Welcome—here are your next steps.”
  • Use subscription_activated for “Your subscription is active.”
  • Use payment_succeeded for “Your payment was received.”
  • Use payment_failed for a factual payment-recovery notice.
  • Use subscription_cancelled for a cancellation confirmation.

Avoid using one generic webhook for every message. Select only the events the endpoint actually handles. This reduces unnecessary delivery attempts, makes logs easier to interpret, and lowers the chance that a new Chargebee event type accidentally invokes a template it was never designed for.

Configure the Chargebee webhook endpoint

Chargebee’s webhook configuration is available from Settings → Configure Chargebee → API Keys and Webhooks, then the Webhooks tab. Create a webhook with Add Webhook, give it a descriptive name such as volanea-subscription-email, and provide your HTTPS receiver URL.

For the code in this article, the URL could be:

https://billing.example.com/webhooks/chargebee

In the event selector, choose subscription_created. Do not leave every event enabled unless this endpoint is intentionally built to route and process every Chargebee event type.

Chargebee supports protecting the webhook URL with HTTP Basic Authentication. Enable that option and set a dedicated username and a long random password. Your receiving endpoint should validate those credentials before it processes a request.

A practical setup has two separate categories of credentials:

CredentialStored whereUsed for
Chargebee webhook Basic Auth username/passwordChargebee webhook configuration and your server-side secret storeLets your receiver reject unauthenticated webhook calls
Volanea secret API keyOnly in your server-side secret storeAuthenticates the outbound request to Volanea

These values are not interchangeable. Chargebee’s Basic Auth credentials protect the inbound hop from Chargebee to your application. The Volanea API key authorizes the outbound hop from your application to Volanea.

Use HTTPS and restrict the receiver

Chargebee recommends HTTPS and/or Basic Authentication for webhook URLs. Use HTTPS regardless of whether you also enable Basic Auth. If your infrastructure supports it, allowlist the published Chargebee webhook source IP ranges for your Chargebee data-center region at a firewall, load balancer, API gateway, or edge layer.

That network control is particularly useful because Chargebee does not provide HMAC webhook signatures. Basic Auth and source-IP controls should be treated as layers, not as a substitute for application-level input validation and deduplication.

Do not put your Volanea API key in the webhook URL, including as a query string such as ?volanea_key=.... URLs are frequently retained in logs, monitoring systems, browser history, proxy records, and support screenshots. A Volanea secret key should never leave the server-side execution environment.

The real Chargebee payload shape

Chargebee delivers a webhook as an HTTP POST with Content-Type: application/json. The top-level object is an event envelope. For a subscription_created event, the important fields are the unique event ID, the event type, and the nested resources under content.

A representative Chargebee payload has this shape:

{
  "id": "ev_subscriptitzugak",
  "occurred_at": 1788952024,
  "source": "admin_console",
  "object": "event",
  "api_version": "v2",
  "event_type": "subscription_created",
  "webhook_status": "not_applicable",
  "content": {
    "subscription": {
      "id": "sub_001",
      "status": "active",
      "billing_period": 1,
      "billing_period_unit": "month",
      "currency_code": "USD",
      "created_at": 1788952024,
      "resource_version": 1788952024000
    },
    "customer": {
      "id": "cust_001",
      "first_name": "Ada",
      "last_name": "Lovelace",
      "email": "ada@example.com",
      "resource_version": 1788952024000
    },
    "invoice": {
      "id": "inv_001",
      "currency_code": "USD",
      "amount_paid": 1000,
      "amount_due": 0
    }
  }
}

Treat that sample as a shape guide, not a schema you can safely hard-code without validation. Chargebee’s event content depends on the event type, and the resource format depends on the api_version indicated in the envelope. Optional fields can also be absent: a customer may not have an email address, a name may be blank, invoice fields differ by billing situation, and a plan or item label may not be included where your template expects it.

Your handler should therefore check the fields it needs instead of constructing strings such as Hello undefined or sending an email to an empty address.

Field mapping used in this guide

The handler below maps these fields:

Chargebee fieldVolanea email use
idStable idempotency key: chargebee:<event id>
event_typeValidation and message classification
content.customer.emailRecipient address
content.customer.first_namePersonalized greeting, with a safe fallback
content.subscription.idSubscription reference in body and logs
content.subscription.statusCurrent status in the message body
content.subscription.billing_periodBilling cadence copy
content.subscription.billing_period_unitBilling cadence copy
content.subscription.currency_codeContextual billing metadata where needed

The email itself should not expose sensitive billing details just because the payload contains them. Avoid including payment method details, full addresses, internal IDs without explanation, or pricing amounts unless the customer needs that information and your template is designed for the relevant currency, tax, and invoice state.

Build the secure webhook-to-email handler

The following Node.js and Express example accepts a Chargebee webhook, checks HTTP Basic Auth, validates the event type and recipient, deduplicates by Chargebee event ID, and sends an email through Volanea.

It uses an in-memory Map only to make the example self-contained. In production, replace it with a durable shared store such as Redis, Postgres, DynamoDB, or another database that remains available across deploys and multiple server instances.

import express from "express";

const app = express();
app.use(express.json({ limit: "1mb" }));

const {
  CHARGEBEE_WEBHOOK_USERNAME,
  CHARGEBEE_WEBHOOK_PASSWORD,
  VOLANEA_API_KEY,
  EMAIL_FROM
} = process.env;

if (!CHARGEBEE_WEBHOOK_USERNAME || !CHARGEBEE_WEBHOOK_PASSWORD) {
  throw new Error("Missing Chargebee webhook Basic Auth credentials");
}

if (!VOLANEA_API_KEY || !EMAIL_FROM) {
  throw new Error("Missing VOLANEA_API_KEY or EMAIL_FROM");
}

// Demo only. Use a durable, shared store in production.
const processedEvents = new Map();
const DEDUPE_WINDOW_MS = (3 * 24 * 60 * 60 * 1000) + (7 * 60 * 60 * 1000);

function basicAuthIsValid(request) {
  const header = request.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 (
    username === CHARGEBEE_WEBHOOK_USERNAME &&
    password === CHARGEBEE_WEBHOOK_PASSWORD
  );
}

function escapeHtml(value) {
  return String(value)
    .replaceAll("&", "&amp;")
    .replaceAll("<", "&lt;")
    .replaceAll(">", "&gt;")
    .replaceAll('"', "&quot;")
    .replaceAll("'", "&#039;");
}

function billingCadence(subscription) {
  const period = subscription.billing_period;
  const unit = subscription.billing_period_unit;

  if (!Number.isInteger(period) || !unit) return "your selected billing schedule";
  return period === 1 ? `every ${unit}` : `every ${period} ${unit}s`;
}

app.post("/webhooks/chargebee", async (request, response) => {
  if (!basicAuthIsValid(request)) {
    return response.status(401).set("WWW-Authenticate", "Basic").json({ error: "Unauthorized" });
  }

  const event = request.body;

  if (event?.object !== "event" || event?.event_type !== "subscription_created") {
    return response.status(204).end();
  }

  const eventId = event.id;
  const customer = event.content?.customer;
  const subscription = event.content?.subscription;
  const recipient = customer?.email?.trim().toLowerCase();

  if (!eventId || !recipient || !subscription?.id) {
    console.error("Chargebee event missing required email fields", {
      eventId,
      eventType: event?.event_type,
      hasCustomerEmail: Boolean(recipient),
      hasSubscriptionId: Boolean(subscription?.id)
    });

    // Return 2XX only if your operational process captures and resolves this exception.
    return response.status(202).json({ received: true, skipped: "missing_required_fields" });
  }

  const previous = processedEvents.get(eventId);
  if (previous && Date.now() - previous < DEDUPE_WINDOW_MS) {
    return response.status(200).json({ received: true, duplicate: true });
  }

  const firstName = customer.first_name?.trim() || "there";
  const cadence = billingCadence(subscription);
  const subscriptionStatus = subscription.status || "created";
  const safeName = escapeHtml(firstName);
  const safeSubscriptionId = escapeHtml(subscription.id);
  const safeStatus = escapeHtml(subscriptionStatus);
  const safeCadence = escapeHtml(cadence);

  const emailPayload = {
    from: EMAIL_FROM,
    to: [recipient],
    subject: "Your subscription has been created",
    html: `
      <h1>Welcome, ${safeName}</h1>
      <p>Your subscription <strong>${safeSubscriptionId}</strong> has been created.</p>
      <p>Current status: <strong>${safeStatus}</strong>.</p>
      <p>Your billing schedule is ${safeCadence}.</p>
      <p>If you need help, reply to this email or contact our support team.</p>
    `,
    text: `Welcome, ${firstName}\n\nYour subscription ${subscription.id} has been created.\nCurrent status: ${subscriptionStatus}.\nYour billing schedule is ${cadence}.\n\nIf you need help, reply to this email or contact our support team.`
  };

  try {
    const volaneaResponse = await fetch("https://api.volanea.com/v1/send", {
      method: "POST",
      headers: {
        "Authorization": `Bearer ${VOLANEA_API_KEY}`,
        "Content-Type": "application/json",
        "Idempotency-Key": `chargebee:${eventId}`
      },
      body: JSON.stringify(emailPayload)
    });

    const responseBody = await volaneaResponse.text();

    if (!volaneaResponse.ok) {
      console.error("Volanea send failed", {
        eventId,
        status: volaneaResponse.status,
        responseBody
      });
      return response.status(500).json({ error: "Email send failed" });
    }

    processedEvents.set(eventId, Date.now());

    console.info("Chargebee subscription email sent", {
      eventId,
      subscriptionId: subscription.id,
      recipient,
      volaneaStatus: volaneaResponse.status
    });

    return response.status(200).json({ received: true, emailed: true });
  } catch (error) {
    console.error("Unexpected webhook handler error", {
      eventId,
      message: error instanceof Error ? error.message : String(error)
    });
    return response.status(500).json({ error: "Webhook processing failed" });
  }
});

app.listen(3000, () => {
  console.log("Listening on http://localhost:3000");
});

The Volanea call uses POST /v1/send, Bearer authentication, JSON content, and an Idempotency-Key header. The event ID is ideal for this key because Chargebee documents it as unique to the event and sends the same event payload again when retries occur.

For endpoint fields, message options, domain setup, and API responses, use the email API reference and setup guides as the source of truth when implementing your production version.

Keep the Volanea API key out of Chargebee and the browser

The Volanea API key should live in the secret store for the environment that runs your webhook handler. Examples include a cloud platform’s encrypted environment-variable service, a container-orchestration secret, a managed secret vault, or a serverless function secret binding.

The correct relationship is:

Chargebee webhook configuration
  → stores webhook destination and optional Basic Auth credentials

Your server-side webhook runtime
  → stores VOLANEA_API_KEY
  → calls Volanea

Do not store the Volanea key in a Chargebee customer custom field, subscription custom field, checkout URL, hosted page JavaScript, Zap step visible to untrusted users, mobile application, or client-side environment variable prefixed for browser exposure. A secret email key can send mail as your account; exposing it can lead to abuse, reputation damage, and an incident that is much harder to unwind than a single bad password.

Secret-management checklist

Use these controls before taking the integration live:

  • Create a dedicated Volanea key for this production webhook workload rather than sharing a personal development key.
  • Store the key as VOLANEA_API_KEY in a server-only secret manager.
  • Use a distinct test key and test Chargebee site during development.
  • Rotate the Volanea key if it is ever committed, copied into a ticket, or exposed in logs.
  • Redact Authorization headers and request bodies from error-monitoring tools.
  • Log event IDs, subscription IDs, HTTP status codes, and provider response IDs—not API keys or full email bodies.
  • Verify the domain used in EMAIL_FROM before sending production mail.

Make duplicate sends impossible in practice

Chargebee retries webhook calls when it does not receive a 2XX response. It can also resend a webhook manually from the web console. That means duplicate delivery is a normal condition your integration must expect, not an unusual edge case.

There are two layers of deduplication in the sample handler:

  1. Application deduplication: The handler records event.id after a successful send and recognizes a later delivery of the same event.
  2. Volanea idempotency: The Volanea request includes Idempotency-Key: chargebee:<event id>, so a retry of the same logical email request can be safely recognized by the sending API.

Use both. Your database record protects the workflow and gives you an audit trail. Volanea idempotency protects the outbound send when the first request may have reached Volanea but your webhook process lost the response during a timeout or network failure.

Chargebee advises maintaining the event-ID idempotency window for at least 3 days and 7 hours because of its retry schedule. Your durable store should retain a processed event ID for that full period at minimum. Many teams retain a longer audit record while using a shorter indexed deduplication TTL for efficiency.

A production database model

A minimal chargebee_webhook_events table or collection might contain:

event_id                 unique
received_at              timestamp
occurred_at              timestamp
api_version              string
event_type               string
subscription_id          string nullable
customer_id              string nullable
recipient_email          string nullable
status                   received | sending | sent | skipped | failed
volanea_idempotency_key  string
volanea_status_code      integer nullable
last_error               string nullable
processed_at             timestamp nullable

Put a unique constraint on event_id. Start a transaction or perform an atomic insert before sending. If another worker receives the same Chargebee retry, the unique insert fails or reveals an existing record, and that worker returns a 2XX without sending a second message.

For high reliability, model sending and sent separately. If a process dies after Volanea receives the request but before your database marks it sent, retry with the same Volanea idempotency key. If the email service has already processed that key, the retry does not create an additional email.

Test with a Chargebee test site first

Use a Chargebee test site and a Volanea test or development configuration before connecting live billing activity to customer-facing messages. A test should prove both the happy path and the recovery path.

Start with the receiver exposed through a trusted HTTPS tunnel or a deployed staging URL. Configure the Chargebee webhook to point at that endpoint, select only subscription_created, and enable Basic Auth. Then create a test customer and subscription through the Chargebee UI or API.

Inspect the received event before finalizing your template. In particular, check:

  • event_type is exactly subscription_created.
  • id is present and stable when the same event is redelivered.
  • content.customer.email contains the intended recipient.
  • content.subscription.id is populated.
  • The event’s api_version matches the resource shape your code expects.
  • The from address is on a verified Volanea sending domain.
  • Both HTML and text versions render correctly in a real inbox.

Do not test by repeatedly creating live subscriptions or by manually posting production-like customer data to an unprotected public endpoint. Billing data can contain personal information, and webhook receivers should be treated as production integration surfaces even in staging.

Test the failure cases deliberately

After the successful send, test these cases one at a time:

  1. Return 500 once from the receiver and confirm Chargebee retries the same event.
  2. Confirm your handler sends only one email despite the retry.
  3. Remove the customer email from a test record and confirm the event is recorded as skipped or failed according to your policy.
  4. Temporarily use an invalid Volanea key and confirm the receiver returns a non-2XX response so Chargebee can retry.
  5. Simulate a network timeout after the Volanea request begins, then verify the same Idempotency-Key is used on recovery.
  6. Send an unrelated Chargebee event to the URL and confirm it is ignored with a safe 2XX response.

When this breaks

A webhook-to-email integration has two independent HTTP hops: Chargebee to your endpoint, and your endpoint to Volanea. Failures are most often caused by treating those hops as one request instead of two systems with separate retry behavior.

Chargebee retries cause duplicate emails

Symptom: A customer receives the same subscription email twice or more.

Cause: Your endpoint sent the email but timed out, crashed, or returned a non-2XX response before Chargebee considered the webhook delivered. Chargebee retried the same event. Manual resend actions can produce the same result.

Fix: Deduplicate using the top-level Chargebee event id in a durable store, and send Idempotency-Key: chargebee:<event id> to Volanea. Retain the Chargebee event ID for at least 3 days and 7 hours. Never generate a fresh random idempotency key on every retry, because that defeats retry-safe sending.

Chargebee times out before your endpoint responds

Symptom: Chargebee shows failed or retried deliveries even though some emails were sent.

Cause: Your endpoint is doing too much work synchronously: slow database operations, template rendering, third-party enrichment, long API timeouts, or blocked network calls can prevent a timely response.

Fix: Keep the receiver focused. Validate the request, atomically persist the event, then either send immediately within a controlled timeout or enqueue a background job. If you queue work, return 2XX only after the event is durably stored. Acknowledge first without persistence only if you are willing to lose messages during an outage.

Customer email or template fields are missing

Symptom: The handler throws an error, sends a malformed email, or sends to the wrong fallback address.

Cause: Event content varies by event type and API version, while individual resource attributes can be absent. A Chargebee customer record may not have an email, first name, invoice, or the item/plan data your content assumes. Some billing configurations also produce payloads that differ from the narrow test scenario used during development.

Fix: Validate required fields before sending. In this guide, content.customer.email and content.subscription.id are required. Use safe defaults for optional fields such as first_name, and avoid referencing plan labels unless you have verified where they appear for your Chargebee product-catalog configuration. Route missing-data events to an alert or review queue instead of silently inventing values.

A stale or out-of-order event sends the wrong message

Symptom: A customer gets a status email that no longer reflects the current subscription state.

Cause: Chargebee webhook payloads are point-in-time snapshots, delivered asynchronously. Events can arrive out of order.

Fix: For state-sensitive messages, compare the resource’s resource_version to the version you have already processed. If the message must reflect the current billing record, retrieve the current resource through Chargebee’s API before composing the email. For an immutable event confirmation such as “subscription created,” the event snapshot is usually the relevant record; for “your subscription is now active,” it is often worth checking the current status.

Unauthorized requests hit the endpoint

Symptom: Your logs show unexpected requests to /webhooks/chargebee.

Cause: Public webhook URLs are discoverable through traffic, configuration mistakes, or generic scanning. Chargebee webhook requests do not include an HMAC signature for cryptographic verification.

Fix: Require HTTPS, enable Chargebee’s Basic Auth protection, verify the Authorization header at your endpoint, and allowlist Chargebee source IPs where practical. Do not rely on a secret query-string token as the only control, and do not log inbound authorization values.

The Volanea send is rejected

Symptom: Your endpoint receives a valid Chargebee event but Volanea returns a non-success response.

Cause: Common causes include an invalid or revoked API key, an unverified sender domain, malformed recipient data, a suppression decision, or an invalid request payload.

Fix: Log the Chargebee event ID, Volanea HTTP status, and safe response metadata. Do not log the API key or full email content. Return a non-2XX response to Chargebee for transient errors so it retries; for permanent data errors, record the event and alert a human rather than retrying indefinitely without changing anything.

Production design choices beyond the first email

Once the first subscription_created flow works, avoid copying and pasting another endpoint for every message. Keep one authenticated Chargebee receiver, then route approved event types to explicit message handlers.

For example:

subscription_created   → welcome / next-steps email
subscription_activated → activation confirmation
payment_succeeded      → payment confirmation or receipt link
payment_failed         → payment-recovery message
subscription_cancelled → cancellation confirmation

Each handler should have its own template, required-field validation, idempotency strategy, and business owner. A payment-failed notice may require a support URL and a careful cadence policy. A cancellation email may need a precise end-of-access date. A welcome message may need onboarding links but should not expose invoice details.

Keep billing logic and email copy coupled only where they need to be. Your code should map structured facts—status, dates, IDs, cadence—into a message. It should not use email templates as a hidden rules engine for determining whether someone has access or whether a payment is final.

Direct webhook versus automation middleware

Chargebee’s native webhooks are the best direct route when you can run a small serverless function, application route, or worker. They give you control over authentication, idempotency, field validation, and logs.

If you cannot operate a webhook receiver, Chargebee also supports Zapier integrations. An automation platform can receive a Chargebee trigger and make an HTTP request to a controlled middleware endpoint. However, do not place the Volanea secret key in a client-visible field or rely on a no-code step without understanding its retry behavior, secret masking, and access controls.

For customer-facing transactional messages, middleware you control remains the stronger default. It gives you a stable place to retain Chargebee event IDs, handle out-of-order events, choose retry behavior, and change templates without exposing credentials.

Conclusion

To send email from Chargebee with Volanea, configure Chargebee’s subscription_created webhook and point it at a secure server-side handler. The handler should authenticate the inbound webhook, validate content.customer.email and subscription data, deduplicate with the top-level event ID, and call Volanea’s POST /v1/send endpoint using a server-only secret key.

The central reliability rule is simple: one Chargebee event ID must produce at most one logical email send. Persist the event ID, reuse it as the Volanea idempotency key, and expect Chargebee retries rather than treating them as exceptions. With that design in place, you can expand from welcome emails to the rest of your subscription lifecycle without turning billing webhooks into a duplicate-email source.

FAQ

Does Chargebee have a native Volanea integration?

No. This setup does not use a Chargebee marketplace app or native plugin. It uses Chargebee’s webhook capability to notify your own server-side endpoint, which then calls Volanea’s REST API.

Which Chargebee event should send a welcome email?

Use subscription_created when the message confirms creation or gives next steps. Use subscription_activated instead if the email promises that the subscription or access is now active.

Where should I save the Volanea API key?

Save it only in a server-side secret manager or environment variable used by your webhook handler. Do not place it in Chargebee custom fields, frontend code, checkout pages, public URLs, or browser-accessible configuration.

How do I stop Chargebee retries from sending duplicate emails?

Use the top-level Chargebee event id as your durable deduplication key, retain it for at least 3 days and 7 hours, and send the same value in Volanea’s Idempotency-Key header for every retry of that event.

What if the Chargebee payload does not include an email address?

Do not send to a guessed or fallback address. Mark the event as skipped or failed, log the event ID and missing field safely, alert the appropriate team, and correct the customer record or workflow before retrying the intended communication.