A PayPal email integration can send a branded confirmation, fulfillment notice, internal alert, or invoice follow-up immediately after a customer pays. The reliable route is PayPal webhooks feeding a server-side endpoint, which verifies the event, retrieves the order details when necessary, and sends the resulting message through Volanea.

PayPal does not provide a native Volanea marketplace app or a webhook setting where you can paste an email API key and have PayPal transform events into email requests. Instead, PayPal delivers a webhook notification to a URL you control. That endpoint is the secure bridge between a completed PayPal payment and the Volanea REST API.

What a PayPal email integration actually connects

The trigger for this integration is PayPal's PAYMENT.CAPTURE.COMPLETED webhook event. A capture is the completed movement of money against a PayPal Checkout order. It is usually the right moment for a payment receipt because it represents a successful capture, rather than an order a buyer merely approved.

The flow has four distinct parts:

  1. A buyer completes checkout and PayPal captures the payment.
  2. PayPal sends a PAYMENT.CAPTURE.COMPLETED event notification to your registered webhook URL.
  3. Your server verifies that notification with PayPal, loads the related order to obtain the payer email address, then builds an email payload.
  4. Your server makes an authenticated Volanea REST API request to send the email.

This design matters because the two services have different jobs. PayPal is the payment system and event producer. Volanea is the email delivery system. Your endpoint applies business rules between them: deciding which event merits an email, selecting the recipient, avoiding duplicates, rendering a receipt, and recording what happened.

For most stores, one capture should create one customer-facing receipt. You can also send an internal message to an operations mailbox for high-value orders, delayed fulfillment, refund review, or a failed recurring-payment workflow. Keep those use cases separate from payment emails so an internal alert cannot accidentally expose customer details to the wrong recipient.

Why webhooks are the supported PayPal trigger

PayPal webhooks are outbound HTTPS notifications associated with an app in the PayPal Developer Dashboard. In the app's Webhooks section, you add an HTTPS listener URL and select the event types that PayPal should send to it. Configure the listener separately in the Sandbox and Live environments because their apps, credentials, webhook IDs, and transactions are separate.

For an initial receipt workflow, select PAYMENT.CAPTURE.COMPLETED. Avoid triggering a receipt from an approval-oriented event unless your business truly considers approval equivalent to payment. An order can be approved before it is captured, and an authorization can exist before money is captured.

The practical setup sequence

Create a PayPal REST app in the Developer Dashboard, then add a webhook to that app. Enter a publicly reachable HTTPS URL such as https://example.com/webhooks/paypal, select PAYMENT.CAPTURE.COMPLETED, and save the webhook. PayPal assigns a webhook ID; retain it as a server-side environment variable because it is required during signature verification.

Next, deploy an endpoint at that URL. It must accept PayPal's POST request, verify the webhook before acting on it, and return a successful HTTP response promptly. The endpoint should not trust a request just because it resembles PayPal JSON or came from an expected-looking IP address. Use PayPal's webhook signature-verification API for every production event.

Finally, add the Volanea sending domain and API credential to your server environment. Follow the email API setup documentation for domain authentication and sending configuration before attempting to deliver production receipts.

Understand the PayPal webhook payload before mapping fields

A PayPal webhook is an event envelope. It includes metadata about the notification and a resource object containing the payment object relevant to that event. A simplified PAYMENT.CAPTURE.COMPLETED notification looks like this:

{
  "id": "WH-2WR32451HC0233532-67976317FL4543714",
  "event_version": "1.0",
  "create_time": "2026-09-23T12:35:11Z",
  "resource_type": "capture",
  "event_type": "PAYMENT.CAPTURE.COMPLETED",
  "summary": "Payment completed for $ 49.00 USD",
  "resource": {
    "id": "3C679366HH908993F",
    "amount": {
      "currency_code": "USD",
      "value": "49.00"
    },
    "final_capture": true,
    "invoice_id": "INV-1042",
    "supplementary_data": {
      "related_ids": {
        "order_id": "5O190127TN364715T"
      }
    }
  }
}

Do not assume this event contains everything needed for a customer email. In particular, the capture event is a payment resource, not a complete customer-profile record. The payer's email address is ordinarily obtained by retrieving the related order with PayPal's Orders API, using resource.supplementary_data.related_ids.order_id.

A retrieved order can include a shape like this:

{
  "id": "5O190127TN364715T",
  "status": "COMPLETED",
  "payer": {
    "email_address": "buyer@example.com",
    "name": {
      "given_name": "Avery",
      "surname": "Ng"
    }
  },
  "purchase_units": [
    {
      "reference_id": "default",
      "invoice_id": "INV-1042",
      "amount": {
        "currency_code": "USD",
        "value": "49.00"
      }
    }
  ]
}

Treat fields as optional even when they appear in examples. Some values depend on the checkout integration, transaction type, permissions, and data your own application supplied when it created the order. A robust integration can use the capture ID as a receipt reference, use invoice_id only when present, and fall back gracefully if a name is absent.

Build the secure webhook-to-email endpoint

The following Node.js example shows the full mapping: verify the PayPal webhook, fetch the related order, extract the payer email, and send a receipt through Volanea. It uses server environment variables rather than browser variables.

import express from "express";

const app = express();
app.use(express.json());

const PAYPAL_API = process.env.PAYPAL_MODE === "live"
  ? "https://api-m.paypal.com"
  : "https://api-m.sandbox.paypal.com";

async function paypalAccessToken() {
  const credentials = Buffer.from(
    `${process.env.PAYPAL_CLIENT_ID}:${process.env.PAYPAL_CLIENT_SECRET}`
  ).toString("base64");

  const response = await fetch(`${PAYPAL_API}/v1/oauth2/token`, {
    method: "POST",
    headers: {
      "Authorization": `Basic ${credentials}`,
      "Content-Type": "application/x-www-form-urlencoded"
    },
    body: "grant_type=client_credentials"
  });

  if (!response.ok) throw new Error(`PayPal OAuth failed: ${response.status}`);
  return (await response.json()).access_token;
}

async function verifyPayPalWebhook(event, accessToken, headers) {
  const response = await fetch(
    `${PAYPAL_API}/v1/notifications/verify-webhook-signature`,
    {
      method: "POST",
      headers: {
        "Authorization": `Bearer ${accessToken}`,
        "Content-Type": "application/json"
      },
      body: JSON.stringify({
        auth_algo: headers["paypal-auth-algo"],
        cert_url: headers["paypal-cert-url"],
        transmission_id: headers["paypal-transmission-id"],
        transmission_sig: headers["paypal-transmission-sig"],
        transmission_time: headers["paypal-transmission-time"],
        webhook_id: process.env.PAYPAL_WEBHOOK_ID,
        webhook_event: event
      })
    }
  );

  if (!response.ok) throw new Error(`PayPal verification failed: ${response.status}`);
  const result = await response.json();
  return result.verification_status === "SUCCESS";
}

app.post("/webhooks/paypal", async (req, res) => {
  try {
    const event = req.body;
    const paypalToken = await paypalAccessToken();

    const verified = await verifyPayPalWebhook(event, paypalToken, req.headers);
    if (!verified) return res.status(400).send("Invalid PayPal signature");

    if (event.event_type !== "PAYMENT.CAPTURE.COMPLETED") {
      return res.sendStatus(204);
    }

    const capture = event.resource;
    const orderId = capture?.supplementary_data?.related_ids?.order_id;
    if (!orderId) throw new Error("Capture event has no related order ID");

    const orderResponse = await fetch(`${PAYPAL_API}/v2/checkout/orders/${orderId}`, {
      headers: { "Authorization": `Bearer ${paypalToken}` }
    });
    if (!orderResponse.ok) throw new Error(`Order lookup failed: ${orderResponse.status}`);

    const order = await orderResponse.json();
    const recipient = order.payer?.email_address;
    if (!recipient) throw new Error("Order has no payer email address");

    const amount = capture.amount?.value ?? order.purchase_units?.[0]?.amount?.value;
    const currency = capture.amount?.currency_code ?? order.purchase_units?.[0]?.amount?.currency_code;
    const invoice = capture.invoice_id ?? order.purchase_units?.[0]?.invoice_id ?? capture.id;
    const firstName = order.payer?.name?.given_name ?? "there";

    const emailResponse = await fetch("https://api.volanea.com/v1/emails", {
      method: "POST",
      headers: {
        "Authorization": `Bearer ${process.env.VOLANEA_API_KEY}`,
        "Content-Type": "application/json"
      },
      body: JSON.stringify({
        from: { email: "receipts@yourdomain.com", name: "Your Store" },
        to: [{ email: recipient, name: firstName }],
        subject: `Payment received — ${invoice}`,
        html: `<p>Hi ${firstName},</p><p>We received your payment of <strong>${amount} ${currency}</strong>.</p><p>Receipt: ${invoice}</p>`,
        text: `Hi ${firstName}, we received your payment of ${amount} ${currency}. Receipt: ${invoice}`
      })
    });

    if (!emailResponse.ok) {
      throw new Error(`Volanea send failed: ${emailResponse.status}`);
    }

    return res.sendStatus(200);
  } catch (error) {
    console.error(error);
    return res.sendStatus(500);
  }
});

The field mapping is deliberate. event.resource.amount supplies the payment amount and currency, event.resource.id is the capture identifier, and the related PayPal order supplies payer.email_address and the payer name. The application-defined invoice_id is preferred for the visible receipt identifier when your checkout created one.

In production, escape any variable included in HTML. The compact example renders values directly to make the mapping visible; a production template should HTML-encode names, product labels, and other user-controlled data before interpolation.

Keep the Volanea API key out of PayPal and the browser

There is no PayPal-side field in this webhook arrangement where the Volanea API key should live. PayPal only needs the endpoint URL and selected event subscriptions. Your Volanea API key belongs in the secret manager or encrypted environment-variable store of the server, function, or queue worker that calls Volanea.

For example, a hosted function can store VOLANEA_API_KEY, PAYPAL_CLIENT_SECRET, and PAYPAL_WEBHOOK_ID as deployment secrets. The public frontend receives none of them. It can call your own checkout or status endpoints, but it must never receive a Volanea bearer token in JavaScript, a mobile app bundle, page markup, analytics payload, or a URL query string.

This is not merely a tidiness rule. Anyone who obtains an email API key may be able to send as your account, consume your quota, and damage sending reputation. A leaked PayPal client secret can also allow unauthorized API access. Rotate exposed credentials, use separate Sandbox and Live values, and limit dashboard access with individual accounts rather than shared logins.

Design the receipt as a transactional email

A payment receipt is a transactional message: it confirms an action the recipient just took. It should be useful without becoming a marketing campaign disguised as a receipt. State what was paid, the amount and currency, a recognizable receipt or order reference, the merchant identity, and a support route.

Recommended receipt fields

At minimum, include:

  • the amount and ISO currency code from the completed capture;
  • a receipt, invoice, order, or capture reference the customer can quote to support;
  • the date and time in a sensible timezone, if you display it;
  • the sender name and a monitored support address or support URL;
  • fulfillment expectations when payment does not mean immediate delivery.

Do not include the full payer email, PayPal access tokens, banking data, or unnecessary webhook JSON in the message. If your business requires tax documentation, calculate tax and line items from your own order database rather than attempting to reconstruct an invoice solely from a capture event.

Use a verified domain for the from address. Domain authentication and aligned sender identity are central to deliverability, especially for receipts that customers may need to find later during disputes or account reconciliation.

When this breaks: retries, timeouts, and missing fields

This integration crosses two network boundaries: PayPal to your webhook endpoint, then your endpoint to Volanea. Expect failures and make duplicate prevention a first-class design concern.

PayPal retries can create duplicate sends

PayPal retries webhook delivery when your listener does not return a successful response. That can happen because your server was unavailable, because a database operation timed out, or because the email provider call completed but your endpoint failed before replying to PayPal. If your handler sends an email before recording that it processed the event, a retry can send the same receipt again.

Store an idempotency record before sending. A practical unique key is the PayPal webhook event id; for business-level protection, also store the capture resource.id plus the event type. Use a database uniqueness constraint, not just an in-memory set. If the record already exists, return a successful response without sending another email.

The strongest pattern is an outbox workflow: verify the webhook, persist the event and an unsent email job in one database transaction, return 200, then let a worker send the job through Volanea. Mark the job sent only after a successful provider response. This makes webhook acknowledgement fast and gives you a controlled retry path for transient email API failures.

Webhook timeouts require a short synchronous path

Do not perform inventory allocation, PDF generation, CRM synchronization, and email delivery serially inside the webhook request. Those operations increase the chance that PayPal treats the notification as failed and retries it. Persist the minimum information needed, acknowledge the verified event, and move slower work to a queue.

If an order lookup is essential to identify the recipient, keep it bounded with a timeout and retry it in the worker when PayPal is temporarily unavailable. Your handler should log PayPal's transmission ID, webhook event ID, order ID, capture ID, and your internal job ID so an operator can trace a specific payment without logging secrets.

Some payload fields are not universal

Do not assume every capture includes an invoice_id, a payer name, shipping details, or even the custom references your application normally supplies. Those fields can differ across checkout configurations and payment flows. Use defensive optional access, provide fallback wording, and decide what to do when no recipient email is available.

A sensible policy is: never guess an email address, never route a customer receipt to a merchant email as a fallback, and create an internal exception record when the payer address is unavailable. That protects privacy and lets support resolve the order with the customer through an appropriate channel.

Test Sandbox and Live as separate systems

Start in PayPal Sandbox with Sandbox app credentials, a Sandbox webhook URL, and a Volanea test-safe recipient such as a team inbox you control. Complete a Sandbox checkout, inspect the received webhook, and compare its event ID, capture ID, related order ID, amount, and recipient mapping to the rendered email.

Test more than the happy path. Send the same saved webhook to a local test endpoint twice and confirm only one email job is created. Remove invoice_id, payer.name, and supplementary_data in fixture tests to validate your fallback behavior. Simulate an order lookup failure and a Volanea HTTP failure to ensure the job remains retryable without causing multiple customer receipts.

When moving to Live, create or select the Live PayPal app, register the Live endpoint, and replace all Sandbox credentials. A common deployment error is using a Live webhook event with Sandbox API credentials or the reverse. Keep environment names visible in logs and use separate secret namespaces to prevent that mismatch.

Operational and privacy considerations

Payment notifications contain personal and financial context, so data minimization is part of good engineering. Store only the identifiers and fields needed to fulfill, support, audit, and send the transaction message. Restrict access to logs, redact recipient addresses where practical, and set retention periods for webhook payloads.

Customer receipts are generally expected after a purchase, but marketing consent is a separate question. Do not automatically add a payer to promotional mail based solely on a PayPal payment unless you have collected valid consent under the rules that apply to your business. Keep marketing systems and transactional notification logic separate.

Monitor the integration at three levels: PayPal webhook delivery and verification failures, internal queue depth and deduplication events, and Volanea delivery outcomes. A payment can be successful while an email is delayed or rejected, so payment status should remain the source of truth for fulfillment. Email is a notification channel, not proof that payment occurred.

Alternatives to a custom webhook endpoint

If you cannot operate a serverless function or application endpoint, an automation platform can act as middleware. The platform must be able to receive PayPal events, call an authenticated HTTP endpoint, securely store the Volanea credential, and preserve enough data to retrieve the related PayPal order when the webhook lacks the payer address.

That route is convenient for a low-volume prototype but has trade-offs. You still need to address duplicate events, error visibility, data handling, and the difference between Sandbox and Live. It can also become harder to implement proper PayPal signature verification and durable idempotency in a no-code workflow.

For a production payment receipt flow, a small serverless endpoint plus a queue is often simpler to audit than a chain of opaque automation steps. It gives you direct control over secret storage, event verification, payload transformation, and retry behavior.

A production checklist

Before enabling customer-facing PayPal email notifications, confirm the following:

  • The PayPal app has a registered HTTPS webhook for PAYMENT.CAPTURE.COMPLETED in the correct environment.
  • The webhook handler calls PayPal's signature-verification endpoint and rejects unsuccessful verification.
  • PayPal client credentials, the webhook ID, and the Volanea API key are stored only in server-side secrets.
  • The handler retrieves the related order when it needs payer.email_address.
  • A durable idempotency key prevents retry-driven duplicate sends.
  • The sender domain is authenticated and the receipt uses a real support route.
  • Missing optional fields result in safe fallback text or an internal exception, never guessed customer data.
  • Sandbox tests cover duplicate delivery, missing fields, provider errors, and the Live cutover.

With these controls in place, the PayPal email integration is straightforward: PayPal reports the completed capture, your service validates and enriches the event, and Volanea delivers a clear transactional email from your authenticated domain.

FAQ

Can PayPal directly send an email through Volanea?

No. PayPal can deliver webhook notifications to an HTTPS endpoint, but it does not offer a native Volanea plugin or a webhook configuration that sends transformed email API requests with a Volanea key. Use a server-side endpoint or carefully configured middleware.

Which PayPal event should send a payment receipt?

Use PAYMENT.CAPTURE.COMPLETED when the receipt should follow a successfully captured payment. Do not use an approval event as a substitute unless your payment model explicitly treats approval as final.

Why fetch the PayPal order after receiving the webhook?

The capture webhook is centered on the capture resource and may not include the payer email address needed for a customer receipt. The related order, identified through supplementary_data.related_ids.order_id, can provide payer.email_address and payer-name data.

How do I stop a PayPal retry from sending two receipts?

Persist a unique record for the webhook event ID and/or capture ID before sending. If the same event arrives again, acknowledge it successfully and do not create another email job. A database-backed outbox is safer than in-memory deduplication.

Should the Volanea API key be saved in PayPal?

No. Store it in the secret manager or environment variables of the server or worker that calls Volanea. PayPal only needs the webhook URL and selected event types; exposing an email API key in client-visible configuration is unsafe.