Send email from Airwallex with Volanea by receiving an Airwallex webhook on your server, verifying it, mapping the payment event into an email request, and calling Volanea’s REST API. This is a backend integration—not a native Airwallex marketplace app—and that distinction is what keeps both payment data and email credentials secure.

Airwallex can send real-time HTTP webhook notifications when events happen in your account. Volanea can accept a transactional email request over HTTPS. Your application sits between them as the trusted decision-maker: it validates the Airwallex signature, determines whether an email should be sent, finds a recipient, and makes one idempotent request to Volanea.

This guide implements a practical confirmation-email flow using the Airwallex payment_intent.succeeded event. The same architecture works for failed payments, settled deposits, transfer status changes, subscription events, reimbursement reports, and other Airwallex webhook types—provided you deliberately choose the event that represents the business moment your recipient should hear about.

What this integration is—and is not

There is no native Volanea app, Airwallex marketplace listing, or one-click connector to install. Airwallex does, however, provide outbound webhooks: when a subscribed event occurs, Airwallex makes an HTTP POST request containing a JSON event payload to the notification URL you configure.

That means the direct integration path is:

  1. A payment reaches its final successful state in Airwallex.
  2. Airwallex emits payment_intent.succeeded.
  3. Airwallex sends the event to https://your-app.example.com/webhooks/airwallex.
  4. Your server verifies the request signature and deduplicates the event.
  5. Your server builds an email payload and sends it to Volanea with POST /v1/send.
  6. Your server returns 200 OK to Airwallex only after it has safely recorded or completed the work.

This is more reliable than trying to put an email API key into a browser checkout, front-end configuration, or an unverified automation step. A payment confirmation is a financial event. It needs a trusted server boundary.

Airwallex webhooks are appropriate here because payment state can change asynchronously. A customer may complete a redirect, authenticate with 3D Secure, use an asynchronous payment method, or close the browser before your frontend has a final answer. The completed payment webhook, rather than a browser callback, is the authoritative trigger for a receipt or confirmation email.

The concrete Airwallex trigger: payment_intent.succeeded

For this guide, the trigger is the Airwallex webhook event named payment_intent.succeeded. It means the PaymentIntent has been fulfilled. Configure that event when you create the webhook subscription.

In the Airwallex web app, a user with the Developer, Admin, or Owner role can go to Developer > Webhooks, choose New webhook, supply a notification URL, select an API version, select the account where applicable, choose payment_intent.succeeded, and submit the webhook subscription.

The event is a better confirmation trigger than payment_intent.created, payment_intent.pending, or payment_intent.updated:

  • payment_intent.created only says a payment object exists.
  • payment_intent.pending means the payment is not final.
  • payment_intent.updated is broad and can happen for non-confirmation changes.
  • payment_intent.succeeded represents the completed-payment transition that a customer expects you to acknowledge.

For an unsuccessful-payment email, use payment_intent.payment_failed as a separate workflow. Do not send both a success email and a failure email from a generic update event, because an event stream can contain multiple state changes for the same PaymentIntent.

What Airwallex sends to your endpoint

Airwallex delivers a JSON webhook envelope by HTTP POST. The envelope has an event ID, an event name, account or organization context, event creation time, API version, and a data.object property containing the resource that triggered the event. For payment events, data.object is the PaymentIntent representation for the selected webhook API version.

A payment event has this general shape:

{
  "id": "evt_100_2019102201549020043_8321220011893766",
  "name": "payment_intent.succeeded",
  "accountId": "78814faa-1b30-4598-a9c8-f0583db8d09d",
  "data": {
    "object": {
      "request_id": "d6a92e2a-02e5-c37b-c977-13796ec7443a",
      "id": "int_aaaat9w2hgh8mzi1111",
      "merchant_order_id": "ORDER-10482",
      "amount": 16.66,
      "currency": "USD",
      "status": "SUCCEEDED",
      "created_at": "2023-01-13T07:32:05+0000",
      "updated_at": "2023-01-13T07:34:09+0000",
      "metadata": {
        "customer_email": "customer@example.com",
        "customer_name": "Avery Chen"
      }
    }
  }
}

The exact fields inside data.object depend on the event type and webhook API version. The envelope field naming shown in product examples can also differ between API-version representations, such as accountId in an older payment example versus account_id in the general Event object documentation. Your email handler should depend on the stable pieces it needs—primarily id, name, and data.object—and you should inspect the sample payload in the Airwallex webhook setup screen for the API version you choose.

The metadata block above is an example of merchant-provided data. If you create PaymentIntents yourself, you can attach a non-sensitive order reference and customer information that your backend is allowed to use. But do not make metadata your only source of truth for a recipient unless you control how every PaymentIntent is created and have tested that your selected API version returns the data you require.

A safer production design is to use merchant_order_id or the PaymentIntent id to look up the order in your own database. Your order record can then provide the customer’s email address, product details, tax, download links, locale, and the exact receipt template to send.

Architecture: Airwallex webhook to Volanea email API

The key idea is to separate payment ingestion from email delivery. Airwallex sends an event. Your endpoint checks that it is genuine and records it. A trusted worker or handler then creates the email request.

Airwallex PaymentIntent succeeds
          |
          v
Airwallex POST /webhooks/airwallex
  x-timestamp + x-signature + JSON event
          |
          v
Your server
  - preserve raw body
  - verify HMAC-SHA256 signature
  - check event name
  - deduplicate event ID
  - look up order and recipient
          |
          v
Volanea POST https://api.volanea.com/v1/send
  Authorization: Bearer <server-side API key>
  Idempotency-Key: airwallex:<event ID>
          |
          v
Volanea processes transactional email

Do not point an Airwallex webhook straight at the Volanea send endpoint. The two APIs have different authentication models and payload formats. Airwallex sends an event envelope signed with its webhook secret; Volanea expects an authenticated send request with email-specific fields. A direct endpoint-to-endpoint connection would also expose no safe place to decide who the recipient is or prevent a payment event from creating duplicate receipts.

The middleware can be a traditional Node.js service, a serverless function, a queue consumer, or an API route in your application. The important requirements are that it can receive the raw request bytes, access secrets, persist deduplication state, and make an outbound HTTPS request.

Prepare the payment data before sending email

A confirmed payment event is not automatically a complete receipt. It tells you that a PaymentIntent succeeded; your application still needs to decide what should appear in the email and whom to send it to.

At PaymentIntent creation time, store a durable relationship between the Airwallex payment and your order. In many systems, that means setting merchant_order_id to your internal order identifier and saving the resulting Airwallex PaymentIntent ID on the order record.

For example, your own order record might look like this:

{
  "id": "ORDER-10482",
  "airwallexPaymentIntentId": "int_aaaat9w2hgh8mzi1111",
  "customer": {
    "email": "customer@example.com",
    "firstName": "Avery"
  },
  "items": [
    { "name": "Pro annual subscription", "quantity": 1, "total": "16.66" }
  ],
  "receiptEmailSentAt": null
}

When the webhook arrives, use data.object.merchant_order_id to retrieve this record. Compare the stored Airwallex PaymentIntent ID to data.object.id before emailing. That extra comparison prevents a mistaken or reused order reference from sending a receipt for the wrong transaction.

Recipient choices and privacy

You have two realistic recipient strategies:

  • Database lookup: Use the merchant order reference to retrieve the recipient from your application database. This is normally best because it avoids depending on optional webhook fields and allows you to render a complete receipt.
  • Controlled metadata: Read an address such as data.object.metadata.customer_email only when your server created the PaymentIntent and validates the value before use. This can work for lightweight integrations, but it is less flexible when fulfillment data lives elsewhere.

Never take a recipient address from arbitrary, client-controlled browser input and immediately use it for payment-triggered email without validating your order ownership and business rules. A malicious client should not be able to cause your system to send payment confirmation emails to an address it selects.

Before accepting an address for a new order, you can also run it through the email address verification tool. Verification is not a replacement for consent, account ownership, or bounce handling, but it can catch plainly malformed and risky addresses before they enter an important transactional flow.

Working Node.js webhook handler and Volanea send call

The following Express example handles the complete hop: it receives the raw Airwallex body, verifies the HMAC signature, accepts only payment_intent.succeeded, looks up an order, creates a Volanea message, and uses the Airwallex event ID as the Volanea idempotency key.

The example uses an in-memory Set only to make the logic readable. Replace it with a database table, Redis key, or another persistent store in production. A process restart must not erase your knowledge of previously handled events.

import crypto from "node:crypto";
import express from "express";

const app = express();

const AIRWALLEX_WEBHOOK_SECRET = process.env.AIRWALLEX_WEBHOOK_SECRET;
const VOLANEA_API_KEY = process.env.VOLANEA_API_KEY;
const VOLANEA_FROM = "Billing <billing@example.com>";

// Demo only. Use a database table with a unique event_id constraint in production.
const processedEventIds = new Set();

function safeEqualHex(a, b) {
  const left = Buffer.from(a || "", "utf8");
  const right = Buffer.from(b || "", "utf8");
  return left.length === right.length && crypto.timingSafeEqual(left, right);
}

function verifyAirwallexSignature(rawBody, timestamp, signature) {
  if (!AIRWALLEX_WEBHOOK_SECRET || !timestamp || !signature) return false;

  // Airwallex signs the concatenation of timestamp and raw request body.
  const expected = crypto
    .createHmac("sha256", AIRWALLEX_WEBHOOK_SECRET)
    .update(`${timestamp}${rawBody}`, "utf8")
    .digest("hex");

  return safeEqualHex(expected, signature);
}

async function findOrderByMerchantOrderId(merchantOrderId) {
  // Replace with your database query.
  if (merchantOrderId !== "ORDER-10482") return null;

  return {
    id: "ORDER-10482",
    airwallexPaymentIntentId: "int_aaaat9w2hgh8mzi1111",
    customer: { email: "customer@example.com", firstName: "Avery" },
    itemName: "Pro annual subscription"
  };
}

app.post("/webhooks/airwallex", express.raw({ type: "application/json" }), async (req, res) => {
  const rawBody = req.body.toString("utf8");
  const timestamp = req.get("x-timestamp");
  const signature = req.get("x-signature");

  if (!verifyAirwallexSignature(rawBody, timestamp, signature)) {
    return res.status(401).json({ error: "Invalid Airwallex webhook signature" });
  }

  const event = JSON.parse(rawBody);

  // Ignore subscribed or redelivered events that are not receipt triggers.
  if (event.name !== "payment_intent.succeeded") {
    return res.sendStatus(200);
  }

  // Airwallex retries failed or timed-out deliveries with the same event ID.
  if (processedEventIds.has(event.id)) {
    return res.sendStatus(200);
  }

  const paymentIntent = event.data?.object;
  const merchantOrderId = paymentIntent?.merchant_order_id;

  if (!event.id || !paymentIntent?.id || !merchantOrderId) {
    return res.status(400).json({ error: "Missing required webhook fields" });
  }

  const order = await findOrderByMerchantOrderId(merchantOrderId);

  if (!order || order.airwallexPaymentIntentId !== paymentIntent.id) {
    return res.status(400).json({ error: "Payment does not match a valid order" });
  }

  const amount = new Intl.NumberFormat("en-US", {
    style: "currency",
    currency: paymentIntent.currency
  }).format(paymentIntent.amount);

  // Field mapping:
  // event.id                         -> Idempotency-Key
  // paymentIntent.merchant_order_id  -> receipt/order reference
  // paymentIntent.amount/currency    -> receipt total
  // order.customer.email             -> Volanea to
  // order.customer.firstName         -> email greeting
  const emailPayload = {
    from: VOLANEA_FROM,
    to: order.customer.email,
    subject: `Payment received for order ${merchantOrderId}`,
    text: `Hi ${order.customer.firstName},\n\nWe received your payment of ${amount} for order ${merchantOrderId}.\n\nItem: ${order.itemName}\nPayment reference: ${paymentIntent.id}`,
    html: `<p>Hi ${order.customer.firstName},</p><p>We received your payment of <strong>${amount}</strong> for order <strong>${merchantOrderId}</strong>.</p><p>Item: ${order.itemName}<br>Payment reference: ${paymentIntent.id}</p>`
  };

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

  if (!emailResponse.ok) {
    const details = await emailResponse.text();
    console.error("Volanea send failed", emailResponse.status, details);
    // Returning non-200 tells Airwallex to retry this webhook later.
    return res.status(502).json({ error: "Email provider request failed" });
  }

  processedEventIds.add(event.id);
  return res.sendStatus(200);
});

app.listen(3000, () => console.log("Listening on port 3000"));

The Volanea request uses POST https://api.volanea.com/v1/send. It supplies a sender, recipient, subject, plain-text alternative, and HTML body. The same endpoint can send to one address or a limited recipient list, but a payment receipt should normally be one message to one customer so recipients never see each other’s addresses.

For request options, API responses, templates, sender setup, and authentication details, use the Volanea API reference and setup guides. Before production, authenticate the domain used in VOLANEA_FROM; a receipt from an unverified or misaligned domain creates avoidable delivery and trust problems.

Where the Volanea API key belongs

The Volanea API key does not belong in Airwallex’s webhook form, in a PaymentIntent metadata field, in frontend JavaScript, in a checkout configuration object, or in a URL query string.

Airwallex’s webhook configuration stores the notification URL and a webhook secret used to authenticate Airwallex-to-your-server requests. The Volanea API key belongs only in the environment or secret manager of the server that owns https://your-app.example.com/webhooks/airwallex.

For example:

AIRWALLEX_WEBHOOK_SECRET=whsec_server_only_value
VOLANEA_API_KEY=volanea_server_only_value
VOLANEA_FROM="Billing <billing@example.com>"

In a managed runtime, configure these as encrypted deployment secrets rather than values committed to .env files or source control. Scope access so that only the webhook service or the worker responsible for notifications can read the Volanea key. When a team member leaves, a deployment target is retired, or a key could have appeared in logs, rotate the key promptly and remove the old value.

Why this matters is simple: a Volanea API key can authorize email sending. If it reaches client-visible configuration, a browser extension, public repository, or a misconfigured automation export, someone else may be able to send mail from your infrastructure. That can damage deliverability, create phishing risk, and expose customer-facing sender identities.

Reliability: acknowledge events without creating duplicate receipts

A webhook is an at-least-once delivery mechanism, not a promise that every event appears exactly once or in sequence. Airwallex treats any response other than 200 OK, as well as a timeout, as a failed delivery and retries with exponential backoff for roughly three days. It can also redeliver an event manually from the Airwallex Events list.

That behavior is correct for payments, but it changes how you build email. If the first attempt sends a receipt successfully but your server times out before returning 200, Airwallex may send the same event again. Without idempotency, the customer receives two receipts.

Use both layers of protection:

  1. Inbound event deduplication: Save event.id in durable storage with a unique constraint. If the ID already exists, return 200 OK without sending another email.
  2. Outbound Volanea idempotency: Send Idempotency-Key: airwallex:<event.id> with the Volanea request. If your handler retries after an uncertain network failure, reuse the exact same key for that exact message.

The database record should be created atomically. A useful table has columns such as airwallex_event_id, event_name, payment_intent_id, merchant_order_id, email_status, volanea_idempotency_key, volanea_response_id, created_at, and sent_at.

For high-volume systems, consider a two-step outbox pattern. The webhook handler verifies the signature, inserts a unique event record and an email job in one database transaction, then returns 200 OK. A background worker sends the Volanea message and marks the job completed. This reduces the risk that a slow provider request causes Airwallex retries, while keeping the payment event and intended email linked in your own durable system.

When this breaks

Every reliable integration has a failure path. The point is not to eliminate every error; it is to make each error observable, safe to retry, and unlikely to produce a misleading customer email.

Airwallex retries create duplicate delivery attempts

Symptom: A customer receives duplicate payment confirmations, often minutes or hours apart.

Cause: Your endpoint returned a non-200 result, timed out, or was manually re-triggered after already sending the email. Airwallex retries failed deliveries and preserves the same event ID for those retries.

Fix: Persist and enforce uniqueness on the Airwallex event ID. Then pass that same ID into Volanea as a stable Idempotency-Key. Do not generate a new random idempotency key on each retry; that defeats the protection because Volanea sees a new request.

The webhook times out before the email provider responds

Symptom: Airwallex marks an event as queued or failed even though an email may have been accepted.

Cause: Your webhook handler waits for a slow network operation, cold start, database lock, or email-provider response before returning 200.

Fix: Make the endpoint small. Verify the signature, persist the event and a job, then acknowledge. Send the email asynchronously from a worker when your volume or latency makes synchronous sending unreliable. If you do send synchronously, set bounded request timeouts and retain enough information to determine whether the Volanea request was accepted before retrying.

Required payload fields are missing

Symptom: The handler cannot find merchant_order_id, a customer email, metadata, or a field expected in data.object.

Cause: Payload content varies by webhook event type and selected API version. Some Airwallex products, account configurations, or integration paths may not include the merchant-specific data your email template assumes.

Fix: Treat the PaymentIntent ID as the minimum reliable join key and look up the order in your own system. Select and pin the webhook API version after testing its payload samples. Log the event ID and field-presence error, but avoid logging full payment payloads or customer data unnecessarily.

Signature verification fails unexpectedly

Symptom: Every webhook returns 401 Invalid Airwallex webhook signature.

Cause: The server parsed and reserialized JSON before calculating the HMAC, used the wrong webhook secret, used a stale secret after rotation, or concatenated fields differently from Airwallex’s documented timestamp-plus-body format.

Fix: Verify against the untouched raw UTF-8 body and the x-timestamp and x-signature headers. Configure raw-body middleware for this route before JSON parsing. Keep the Airwallex webhook secret separate from the Volanea API key; they have different purposes and must never be interchangeable.

A payment success email contains incorrect order details

Symptom: The total, item list, or recipient does not match the payment.

Cause: Your handler trusted a loose merchant_order_id, reused an order identifier, or fetched data without confirming that the stored PaymentIntent ID matches the event’s data.object.id.

Fix: Store the PaymentIntent ID when you create it, compare it on webhook receipt, and make order IDs immutable. Format currency using the event’s currency value, not a hard-coded currency. For receipts with tax or invoice details, pull authoritative amounts from your order ledger rather than reconstructing business records from an email template.

Testing the full payment-to-email flow

Start in Airwallex Sandbox and send to an inbox you control. Do not begin with a production customer or a live payment as your first test. Create a test PaymentIntent, complete the supported sandbox payment flow, and confirm that your selected event appears in the Airwallex event log.

Your acceptance checklist should include:

  • The Airwallex event name is payment_intent.succeeded.
  • The request reaches the configured HTTPS notification URL.
  • The signature validates using the raw body.
  • The event ID is saved once in your durable store.
  • The event’s PaymentIntent ID matches the stored order record.
  • The recipient comes from a trusted application record.
  • The Volanea request contains the correct sender, recipient, subject, text, HTML, and idempotency key.
  • The email displays correctly in a desktop client and a mobile client.
  • Replaying the same Airwallex event does not send a second receipt.
  • A simulated Volanea failure produces a recoverable job or a controlled Airwallex retry.

Airwallex retains webhook events for a limited period and exposes delivery status in Settings > Developer > Events. Use that view to inspect the request payload, HTTP response status, and delivery outcome. It is especially useful when testing signature errors or confirming that a non-200 response was retried as expected.

On the Volanea side, inspect the send log to distinguish accepted sends from recipient-level suppression, unsubscribe, quota, or later delivery outcomes. An API acceptance response means the message entered the sending pipeline; it is not identical to an inbox-placement guarantee.

Alternatives when you cannot host a webhook endpoint

If you cannot deploy a small service, Airwallex also has Zapier connectivity for several triggers. A low-code workflow can use an Airwallex trigger and a webhook action to call a middleware endpoint, or an automation platform can call an email API through a custom request feature.

However, avoid putting a long-lived Volanea key directly into a broad, client-managed automation setup unless the platform provides an appropriate encrypted secret mechanism, access controls, and audit trail. A middleware endpoint remains the cleaner choice for payment emails because it can verify Airwallex signatures, perform database lookups, enforce idempotency, and keep the email credential under your infrastructure’s control.

Automation is most suitable when the email is operational rather than a formal customer receipt—for example, notifying a finance channel about a failed transfer or alerting an internal team that a card is approaching its configured limit. For money-related messages sent to customers, use the direct webhook-to-server architecture whenever possible.

Conclusion

To send email from Airwallex with Volanea, use an Airwallex webhook as the trigger and your own server as the secure translation layer. Subscribe to payment_intent.succeeded, verify the request using the Airwallex webhook secret, look up the trusted order and recipient, and call Volanea’s POST /v1/send endpoint using a server-side API key.

The implementation is small, but its reliability details are not optional. Preserve the raw body for HMAC verification, do not trust a browser callback as final payment proof, store the Airwallex event ID, reuse it as the Volanea idempotency key, and make missing fields a controlled exception rather than a reason to guess. Those choices turn a simple receipt flow into dependable email infrastructure.

FAQ

Does Airwallex have a native Volanea integration?

No. This setup does not use a native app or marketplace plugin. It uses Airwallex outbound webhooks plus a server-side endpoint that calls the Volanea REST API.

Which Airwallex event should send a payment confirmation email?

Use payment_intent.succeeded for a confirmed online-payment message. It represents a fulfilled PaymentIntent, unlike creation, pending, or generic update events.

Where should I store the Volanea API key?

Store it as a server-side environment secret or in your cloud secret manager. Do not put it in Airwallex webhook settings, PaymentIntent metadata, frontend code, browser configuration, or source control.

Why do I need both webhook deduplication and an idempotency key?

Airwallex can retry the same event after a timeout or non-200 response. Saving the event ID prevents duplicate processing, while the Volanea Idempotency-Key protects the outbound send if your service retries an uncertain email request.

Can I use customer email from Airwallex PaymentIntent metadata?

You can when your backend creates and controls that metadata, but a database lookup using merchant_order_id and the PaymentIntent ID is generally safer. It gives you a durable recipient source and complete receipt data even when optional webhook fields differ by API version.