Send email from Qonto by subscribing to Qonto transaction webhooks, receiving the signed event in a server-side endpoint, and calling Volanea’s REST API from that endpoint. There is no native Volanea app, Qonto Marketplace installation, or direct in-dashboard Qonto-to-Volanea connector—but Qonto’s Business API webhooks provide the outbound HTTP capability needed for a dependable integration.
This guide builds a practical finance-notification flow: when Qonto emits a transactions.created event, your relay verifies Qonto’s signature, records the event, and sends an internal email through Volanea. You can adapt the same pattern for payment alerts, reconciliation queues, invoice follow-up workflows, or operations notifications.
What this Qonto and Volanea integration does
The integration has three components:
- Qonto Business API webhooks notify your server when an event occurs.
- Your relay endpoint authenticates the webhook, filters events, prevents duplicate processing, and prepares email content.
- Volanea’s
POST /v1/sendendpoint queues the transactional email.
The concrete trigger in this guide is Qonto’s transactions.created event. Qonto delivers that event in a webhook whose top-level type is v1/transactions and whose data.event value is created. The event represents a newly created transaction—not a form submission, CRM stage change, or generic workflow trigger.
That distinction matters. Qonto is a financial platform, so its transaction webhook payload includes banking and transaction context such as amount, currency, side, operation type, counterparty label, reference, and status. It does not reliably contain a customer email address. For that reason, the safest first implementation sends an internal alert to a configured finance or operations mailbox rather than attempting to email a transaction counterparty.
A good initial use case is:
When a new completed credit transaction is created in Qonto, send the finance team a notification containing the amount, currency, counterparty name, reference, and Qonto transaction ID.
Once that works, you can add routing rules. For example, send high-value credits to treasury, debit card transactions to expense review, or transfers carrying a specific reference prefix to a billing queue.
Why the direct webhook route is the right architecture
Qonto supports outbound webhooks through its Business API. You create a webhook subscription with a public HTTPS callback_url, select event types such as v1/transactions, and Qonto sends matching events to that endpoint as signed HTTP POST requests.
That means you do not need to poll Qonto every few minutes, scrape the dashboard, or depend on a browser session. The webhook is the source event, while the relay is the security and reliability boundary between Qonto and your email provider.
Do not point Qonto directly at Volanea’s send endpoint. Qonto’s webhook JSON is a financial-event payload, while Volanea expects an email-send payload. More importantly, a direct call would have nowhere safe to add your Volanea secret key, choose recipients, validate business rules, or handle duplicate events.
The relay can be a small Node.js service, serverless function, container endpoint, or worker. It should do only a few jobs quickly:
- preserve the raw request body for signature verification;
- verify
X-Qonto-Signaturebefore reading event data; - acknowledge valid webhooks promptly with a
2xxresponse; - store or enqueue the event for asynchronous handling;
- deduplicate on Qonto’s webhook event ID;
- call Volanea with a stable
Idempotency-Key; - record the Volanea response and delivery correlation data.
This is not unnecessary infrastructure. Qonto waits only a short time for webhook acknowledgement and retries failed deliveries. A relay prevents slow template rendering, database work, email API latency, or temporary Volanea errors from turning into repeated webhook deliveries and duplicate finance alerts.
The exact Qonto trigger and payload shape
For this implementation, subscribe to the Qonto webhook type v1/transactions. Qonto documents two supported events for this type: transactions.created and transactions.updated. In the delivered JSON, use the combination of type: "v1/transactions" and data.event: "created" to identify the creation event.
A representative Qonto transaction webhook has this shape:
{
"id": "123e4567-e89b-12d3-a456-426614174000",
"subscription_id": "123e4567-e89b-12d3-a456-426614174000",
"organization_id": "123e4567-e89b-12d3-a456-426614174000",
"membership_id": "123e4567-e89b-12d3-a456-426614174000",
"type": "v1/transactions",
"created_at": "2025-01-24T10:55:00Z",
"data": {
"event": "created",
"id": "123e4567-e89b-12d3-a456-426614174000",
"transaction_id": "super-transaction-6576",
"amount": 50.01,
"currency": "EUR",
"local_amount": 45.01,
"local_currency": "USD",
"side": "debit",
"settled_balance": 100.02,
"operation_type": "transfer",
"status": "completed",
"label": "ACME Corp",
"clean_counterparty_name": "ACME Corp",
"note": "Business lunch",
"reference": "INV-1234",
"settled_at": "2025-01-24T10:54:00Z",
"emitted_at": "2025-01-24T10:54:00Z",
"updated_at": "2025-01-24T10:55:00Z",
"attachment_ids": ["attachment-1", "attachment-2"],
"subject_type": "Transfer",
"bank_account_id": "123e4567-e89b-12d3-a456-426614174000",
"transfer": {
"counterparty_account_number": "123456789",
"counterparty_account_number_format": "account_number",
"counterparty_bank_identifier": "987654321",
"counterparty_bank_identifier_format": "bic"
}
}
}
Treat this as an event notification, not as a complete accounting record or an email-ready object. The fields you should use depend on your policy. For an internal alert, data.amount, data.currency, data.side, data.status, data.operation_type, data.clean_counterparty_name, data.reference, and data.transaction_id are useful. The top-level id is especially important because it identifies the webhook delivery event and makes an excellent deduplication key.
Avoid placing full account numbers, IBANs, transaction notes, attachment identifiers, or other sensitive information into email unless the recipient truly needs it. Email inboxes are often less tightly controlled than banking access, and forwarding creates a second copy outside your finance system.
Create the Qonto webhook subscription
Creating a Qonto Business API webhook subscription is an API operation. It is not a Volanea installation step inside the Qonto product interface.
The subscription endpoint is:
POST https://thirdparty.qonto.com/v2/webhook_subscriptions
The webhook subscription requires the webhook OAuth scope plus the scope required for the event family. For transaction webhook events, Qonto specifies organization.read in addition to webhook.
Create a dedicated, high-entropy webhook secret and keep it in your server-side secret manager. Send that secret when creating the subscription, then use the same secret to verify inbound Qonto signatures.
curl --request POST \
--url https://thirdparty.qonto.com/v2/webhook_subscriptions \
--header "Authorization: Bearer $QONTO_ACCESS_TOKEN" \
--header "Content-Type: application/json" \
--data '{
"callback_url": "https://automation.example.com/webhooks/qonto",
"types": ["v1/transactions"],
"secret": "'"$QONTO_WEBHOOK_SECRET"'",
"description": "Send internal email for new Qonto transactions"
}'
Keep the subscription narrow. This workflow only needs v1/transactions; subscribing to every available Qonto event type creates unnecessary traffic and raises the chance that an unrelated banking event triggers an email rule by accident.
Also remember that Qonto does not backfill earlier events when you create a new webhook subscription. If you need historical transactions, obtain them through the relevant Qonto HTTP API endpoint and process them as a separate, clearly marked import. Do not expect webhook registration to replay your account history.
Store secrets in the relay, not in client-visible configuration
Your Volanea API key lives nowhere on the Qonto side in this direct architecture. Qonto sends signed webhook requests to your callback URL; it is not configured with a Volanea API key and it does not transform transaction data into an authenticated Volanea email request.
Store the Volanea key only in your relay’s server-side secret store, environment configuration, or deployment platform secret manager. For example:
VOLANEA_API_KEY=sk_live_...
QONTO_WEBHOOK_SECRET=whsec_...
FINANCE_ALERT_TO=finance-alerts@example.com
MAIL_FROM=Qonto Alerts <alerts@notifications.example.com>
The Volanea secret key must never appear in:
- browser JavaScript, mobile application bundles, or public configuration files;
- a webhook URL query string;
- a Qonto transaction note or payment reference;
- source-control commits, issue screenshots, or client-side analytics events;
- an automation step whose execution logs are visible to untrusted users.
A client-visible key could allow anyone who obtains it to send email through your account, consume sending capacity, impersonate your verified sender domain, or create a deliverability incident. A server-side relay restricts the key to the one workload that needs it.
Use a dedicated Volanea key for this workflow rather than sharing a broad credential with every application. Rotate it if it is exposed, remove old keys after replacement, and keep a record of which service owns each key. Before production, verify the sending domain used in MAIL_FROM; Volanea requires an appropriate verified sending domain for authenticated sending. Review the Volanea API setup guides for the current send endpoint and domain configuration details.
Working code: verify Qonto, map fields, and send through Volanea
The example below uses Node.js with Express. It receives the raw Qonto payload, validates the timestamped HMAC signature from X-Qonto-Signature, filters to created transactions, and submits one email to Volanea.
It intentionally sends to a fixed internal recipient from FINANCE_ALERT_TO. Qonto’s transaction event should not be treated as a customer-contact record, and this avoids accidentally sending transaction details to an address that is missing, stale, or unrelated to the payment.
import "dotenv/config";
import crypto from "node:crypto";
import express from "express";
const app = express();
// Keep raw bytes. Parsing JSON before verification can change the signed content.
app.post(
"/webhooks/qonto",
express.raw({ type: "application/json" }),
async (req, res) => {
const rawBody = req.body.toString("utf8");
const signatureHeader = req.get("X-Qonto-Signature");
if (!verifyQontoSignature(signatureHeader, rawBody)) {
return res.status(401).json({ error: "Invalid Qonto signature" });
}
const event = JSON.parse(rawBody);
// Accept the delivery quickly. In production, persist event.id into a queue
// or durable database before returning this response.
res.status(204).end();
// The concrete Qonto trigger for this guide.
if (event.type !== "v1/transactions" || event.data?.event !== "created") {
return;
}
// Example policy: only notify on completed incoming transactions.
if (event.data.status !== "completed" || event.data.side !== "credit") {
return;
}
// Replace with a durable unique insert, such as an events table with a unique
// constraint on qonto_event_id. If it already exists, stop without sending.
const alreadyProcessed = await eventAlreadyProcessed(event.id);
if (alreadyProcessed) return;
await markEventProcessing(event.id);
const amount = new Intl.NumberFormat("en-GB", {
style: "currency",
currency: event.data.currency
}).format(event.data.amount);
// Field mapping from Qonto's transaction payload to the email content.
const transactionId = event.data.transaction_id;
const counterparty = event.data.clean_counterparty_name || event.data.label || "Unknown counterparty";
const reference = event.data.reference || "No reference";
const settledAt = event.data.settled_at || "Not supplied";
const emailPayload = {
from: process.env.MAIL_FROM,
to: process.env.FINANCE_ALERT_TO,
subject: `Qonto credit received: ${amount}`,
text: [
"A completed credit transaction was created in Qonto.",
"",
`Amount: ${amount}`,
`Counterparty: ${counterparty}`,
`Reference: ${reference}`,
`Operation type: ${event.data.operation_type}`,
`Transaction ID: ${transactionId}`,
`Settled at: ${settledAt}`,
`Qonto event ID: ${event.id}`
].join("\n"),
html: `
<h1>Completed Qonto credit</h1>
<table>
<tr><th align="left">Amount</th><td>${escapeHtml(amount)}</td></tr>
<tr><th align="left">Counterparty</th><td>${escapeHtml(counterparty)}</td></tr>
<tr><th align="left">Reference</th><td>${escapeHtml(reference)}</td></tr>
<tr><th align="left">Operation type</th><td>${escapeHtml(event.data.operation_type)}</td></tr>
<tr><th align="left">Transaction ID</th><td>${escapeHtml(transactionId)}</td></tr>
<tr><th align="left">Settled at</th><td>${escapeHtml(settledAt)}</td></tr>
<tr><th align="left">Qonto event ID</th><td>${escapeHtml(event.id)}</td></tr>
</table>
`
};
const volaneaResponse = await fetch("https://api.volanea.com/v1/send", {
method: "POST",
headers: {
"Authorization": `Bearer ${process.env.VOLANEA_API_KEY}`,
"Content-Type": "application/json",
// Stable across Qonto retries: one event maps to one email operation.
"Idempotency-Key": `qonto-transaction-created-${event.id}`
},
body: JSON.stringify(emailPayload)
});
if (!volaneaResponse.ok) {
const details = await volaneaResponse.text();
await markEventFailed(event.id, details);
throw new Error(`Volanea send failed: ${volaneaResponse.status} ${details}`);
}
const result = await volaneaResponse.json();
await markEventSent(event.id, result);
}
);
function verifyQontoSignature(header, rawBody) {
if (!header) return false;
const pieces = Object.fromEntries(
header.split(",").map((part) => {
const [key, value] = part.trim().split("=");
return [key, value];
})
);
if (!pieces.t || !pieces.v1) return false;
// Reject stale deliveries to reduce replay risk.
const ageInSeconds = Math.abs(Math.floor(Date.now() / 1000) - Number(pieces.t));
if (!Number.isFinite(ageInSeconds) || ageInSeconds > 300) return false;
const signedPayload = `${pieces.t}.${rawBody}`;
const expected = crypto
.createHmac("sha256", process.env.QONTO_WEBHOOK_SECRET)
.update(signedPayload, "utf8")
.digest("hex");
const received = Buffer.from(pieces.v1, "hex");
const calculated = Buffer.from(expected, "hex");
return received.length === calculated.length && crypto.timingSafeEqual(received, calculated);
}
function escapeHtml(value) {
return String(value)
.replaceAll("&", "&")
.replaceAll("<", "<")
.replaceAll(">", ">")
.replaceAll('"', """)
.replaceAll("'", "'");
}
// Implement these with your database, queue, or durable workflow system.
async function eventAlreadyProcessed(qontoEventId) { return false; }
async function markEventProcessing(qontoEventId) {}
async function markEventSent(qontoEventId, result) {}
async function markEventFailed(qontoEventId, details) {}
app.listen(3000, () => console.log("Listening on port 3000"));
The mapping is deliberate:
| Qonto field | Volanea email use |
|---|---|
data.amount + data.currency | Formatted amount in subject and message body |
data.clean_counterparty_name or data.label | Counterparty line in the email |
data.reference | Reconciliation reference |
data.operation_type | Transaction classification |
data.transaction_id | Internal lookup and audit reference |
top-level id | Database deduplication key and Volanea Idempotency-Key |
data.settled_at | Timing context for finance staff |
The Idempotency-Key deserves special attention. It uses Qonto’s top-level event ID, not the transaction ID. A transaction can later generate an update event, and your rules may eventually notify about both creation and change. Distinguishing event deliveries lets you define that policy explicitly.
Choose safe notification rules before sending
A transaction-created webhook does not automatically mean “send an email.” Production rules should decide which events are email-worthy and who should receive them.
Start with a narrow policy, such as completed credits over a threshold:
const isCompletedIncomingPayment =
event.type === "v1/transactions" &&
event.data.event === "created" &&
event.data.status === "completed" &&
event.data.side === "credit" &&
event.data.amount >= 500;
You can add criteria based on Qonto fields, but avoid assuming every transaction has every optional-looking value. A card transaction may not have the same nested transfer information as a bank transfer. A payment can have no useful free-text reference. Counterparty formatting can vary depending on payment rail and source data.
Useful routing policies include:
- Credit notification: send to accounts receivable when an incoming completed credit has a payment reference beginning with an invoice prefix.
- Large debit review: email finance leadership when a debit exceeds an approved threshold.
- Expense workflow: notify an expense-review mailbox when a card or transfer transaction is created without an expected reference pattern.
- Exception alert: notify operations only when
status,side, oroperation_typefalls outside your normal reconciliation rules.
For user-facing payment confirmation, do not use a Qonto transaction webhook alone as the customer identity source. Instead, join data.reference or a known transaction identifier to your own billing database, retrieve the customer’s opted-in email address there, and send a message only after your business rules confirm the payment state.
When this breaks: Qonto retries, timeouts, and incomplete fields
Every webhook integration needs a failure plan. The important issues are specific to this hop: Qonto delivers a signed event to your endpoint, your endpoint processes it, and then Volanea accepts an email request.
Qonto retries can create duplicate send attempts
Qonto retries a webhook if your endpoint does not acknowledge it successfully within its delivery window. Its webhook guidance says it waits up to one second for acknowledgement, then retries failed deliveries with exponential backoff for several days.
That means this unsafe pattern will duplicate mail:
- Qonto calls your endpoint.
- Your endpoint calls Volanea.
- Volanea accepts the email.
- Your endpoint crashes or times out before returning
2xxto Qonto. - Qonto retries the same event.
- Your endpoint sends the email again.
Use two layers of protection:
- A database-backed unique constraint or durable event ledger keyed by the top-level Qonto webhook
id. - Volanea’s
Idempotency-Keyheader using the same stable event ID.
The database prevents repeating your business workflow. The Volanea idempotency key protects the outbound email operation if your worker retries after an ambiguous network failure.
Slow webhook handlers cause avoidable deliveries and retries
Do not perform slow work before acknowledging Qonto. Rendering large documents, fetching unrelated systems, querying a slow database, or waiting on an email API response can exceed the acknowledgement window.
The durable version of the architecture is:
- Verify Qonto’s signature from the raw body.
- Insert the event into a queue or event table with a unique event ID.
- Return
204 No Contentor another2xximmediately. - Let a worker apply filters and call Volanea.
- Retry worker failures independently of Qonto delivery.
The illustrative code sends after responding in order to show the complete mapping in one place. For production traffic, replace the placeholder persistence functions with a real queue or database transaction before the acknowledgement is returned.
Missing or unsuitable payload fields can break templates
A Qonto transaction payload is not guaranteed to fit a single universal email template. For example, a rule that assumes data.transfer.counterparty_account_number exists may fail for a transaction that is not a transfer. A free-text reference may be empty. A label may be absent or not appropriate for an external email.
Use defaults for display-only fields, validate values before selecting a route, and maintain separate templates or renderers for different operation_type and subject_type categories. Most importantly, do not use a missing field as an excuse to send a vague or misleading message.
If a required business field is unavailable, route the event to an exception queue rather than emailing an incomplete confirmation. For example, if an invoice-match rule depends on reference and no reference is present, email an internal review address with a neutral subject such as “Qonto payment needs reconciliation” instead of telling a customer that an invoice has been paid.
Signature failures usually come from body handling
Qonto signs a string formed from the timestamp, a period, and the raw request body. If your framework parses and reformats JSON before you compute the HMAC, the bytes no longer match what Qonto signed.
Preserve raw bytes, verify first, and parse JSON only after the signature comparison succeeds. Also reject stale timestamps and compare signatures using a timing-safe comparison. These steps protect the endpoint from forged or replayed requests.
A successful Volanea API response is not final mailbox delivery
A successful POST /v1/send means Volanea accepted or queued the message for sending; it is not proof that the recipient’s mailbox accepted the message. Suppression status, recipient mailbox rules, bounces, complaints, and quota conditions can still affect the final outcome.
For operationally important alerts, store the Volanea message result alongside the Qonto event ID and consume Volanea delivery events in your application. That gives you an audit trail from Qonto event to email request to final delivery outcome.
Testing the complete workflow safely
Test in layers so a banking event, security configuration, and email configuration do not all fail at once.
First, test webhook delivery only
Set up a temporary HTTPS endpoint and create a Qonto subscription for v1/transactions. Confirm that Qonto can reach the callback URL and that the request contains the expected X-Qonto-Signature header. Do not move immediately to production financial notifications.
Then validate your signature implementation with Qonto’s published signature format: parse t and v1, reconstruct ${timestamp}.${rawBody}, and calculate HMAC-SHA256 using your subscription secret.
Then, test the Volanea send independently
Send a basic internal email through POST /v1/send from your relay using a verified sender domain and a mailbox you control. Confirm that the API key is available only in server-side configuration and that the send is visible in your Volanea records.
Use a distinct test sender or subject prefix, such as [TEST] Qonto transaction alert, so test notifications cannot be confused with production finance notices.
Finally, test realistic transaction conditions
Your test cases should cover more than one happy-path event:
- A completed credit transaction that should send an alert.
- A debit transaction that should be filtered out by the credit-only rule.
- An event replayed with the same webhook ID, which should produce no second email.
- A transaction with no reference, which should use the fallback or exception route.
- A malformed or stale signature, which should receive a non-2xx response and create no email.
- A temporary Volanea error, which should be retried by your worker with the same idempotency key.
Keep a test ledger showing the Qonto event ID, transaction ID, selected rule, Volanea idempotency key, send response, and final email status. This is more useful for troubleshooting than searching mailbox threads after the fact.
Direct webhook relay versus Zapier or Make
Qonto also supports automation ecosystems including Zapier and Make. Those can be useful when you do not want to operate a webhook endpoint, but they are not a substitute for a secret-safe architecture.
A no-code route typically looks like this:
Qonto transaction trigger → Zapier or Make scenario → secure webhook relay → Volanea POST /v1/send
Do not place a full-power Volanea secret key in a client-side browser step or expose it in a broadly accessible automation workspace. If your automation platform supports encrypted connection secrets and restricted workspace access, it may be acceptable to call Volanea from the platform. But a relay still gives you better signature verification control, payload validation, observability, idempotency, and version-controlled business logic.
Choose the direct webhook relay when:
- you need signed-event verification and a detailed audit trail;
- emails are tied to sensitive finance operations;
- duplicate sends would be disruptive;
- you need complex recipient lookup from your own database;
- you want test coverage, reviewable code, and precise retry behavior.
Choose an automation platform when:
- the workflow is low-risk and internal;
- your team already governs that platform carefully;
- the trigger and recipient mapping are simple;
- you understand where secrets and execution logs are stored;
- you still have an idempotency strategy for retries.
For most payment-related notifications, a small relay is the more durable choice. It makes the Qonto-to-email boundary explicit instead of hiding financial decision logic inside a visual automation step.
Operational checklist before production
Before enabling live Qonto transaction emails, confirm the following:
- Your callback URL is public, HTTPS-enabled, and accepts POST requests.
- The Qonto subscription is limited to
v1/transactionsunless additional event families are truly needed. - Your OAuth authorization includes
webhookandorganization.readfor transaction webhooks. QONTO_WEBHOOK_SECRETis stored securely and is not logged.- Raw-body signature verification happens before JSON parsing.
- The relay rejects stale timestamps and invalid HMAC signatures.
- A database or queue deduplicates on top-level Qonto event ID.
- Every Volanea email operation uses a stable
Idempotency-Key. - Your Volanea API key is server-side only and scoped to this integration where possible.
- The sender domain is verified and the
fromaddress is appropriate for finance notifications. - Rules filter transaction types and statuses before attempting a send.
- Message templates avoid exposing unnecessary account or payment data.
- You record the Qonto event ID, Volanea response, and final notification outcome.
Once this foundation is in place, you can add stored Volanea templates, recipient routing, rate controls, escalation rules, and delivery-event monitoring without changing the core security model.
Conclusion
To send email from Qonto with Volanea, use Qonto’s Business API webhooks—not a nonexistent native marketplace app—and connect them to a secure server-side relay. Subscribe to v1/transactions, identify data.event: "created", verify Qonto’s X-Qonto-Signature, persist and deduplicate the event, then call Volanea’s POST /v1/send with a stable idempotency key.
The critical design choice is keeping financial event processing and email sending separate from client-visible configuration. Qonto provides the signed event; your relay applies business logic; Volanea handles transactional email delivery. That separation gives you a safer path for finance alerts today and more sophisticated transaction workflows later.
FAQ
Does Qonto have a native Volanea integration?
No. Qonto does not provide a native Volanea app, marketplace listing, or one-click connector for this workflow. Use Qonto Business API webhooks with a secure relay that calls Volanea’s REST API.
What Qonto event should trigger the email?
This guide uses the transactions.created event delivered under the v1/transactions webhook type. In the payload, check for type === "v1/transactions" and data.event === "created".
Can I email the transaction counterparty automatically?
Not safely from the webhook alone. Qonto’s transaction event is not a customer-contact record and does not reliably supply a recipient email address. Join the transaction reference or identifier to your own customer database, verify consent and payment state, then send only when the match is valid.
Why do I need both Qonto event deduplication and Volanea idempotency?
Qonto may retry a webhook delivery if your endpoint does not acknowledge it in time. Your own event ledger prevents duplicate workflow processing, while Volanea’s Idempotency-Key prevents duplicate email side effects during uncertain send retries.
Where should I keep the Volanea API key?
Keep it in server-side environment variables or a secrets manager used by your relay. Do not store it in browser code, public config, Qonto transaction metadata, or any location visible to end users.