Automating payments, such as supplier payouts, salary payments, or refunds to eliminate manual processing time and reduces the risk of human error. In this guide, we will list beneficiaries to find the right recipient, verify the payee identity through the Verification of Payee (VoP) service, and submit the transfer.
Example: A freelance management platform automatically pays contractors every Friday based on approved timesheets, without anyone logging into Qonto manually.
Prerequisites:
- At least one beneficiary with
status: validated.
- Scopes:
organization.read, payment.write
Beneficiaries can only be trusted through the Qonto web app, unless you are are an Embed partner with the beneficary.trust scope. Learn more about sensitive scopes.
List SEPA beneficiaries
Retrieve the list of validated beneficiaries and find the one you want to pay. You can filter by IBAN or name to narrow down the results.Endpoint: List SEPA beneficiariesOAuth scope required: organization.read
import requests
BASE_URL = "https://thirdparty.qonto.com/v2"
headers = {"Authorization": "Bearer {your_access_token}"}
response = requests.get(
f"{BASE_URL}/sepa/beneficiaries",
headers=headers,
params={
"iban[]": "FR7616798000010000005663951", # Filter by IBAN
"status[]": "validated",
"per_page": 25,
},
)
response.raise_for_status()
beneficiaries = response.json()["beneficiaries"]
beneficiary = beneficiaries[0]
beneficiary_id = beneficiary["id"]
beneficiary_name = beneficiary["name"]
print(f"Found beneficiary: {beneficiary['name']} ({beneficiary_id})")
const BASE_URL = "https://thirdparty.qonto.com/v2";
const headers = { Authorization: "Bearer {your_access_token}" };
const params = new URLSearchParams();
params.append("iban[]", "FR7616798000010000005663951"); // Filter by IBAN
params.append("status[]", "validated");
params.append("per_page", 25);
const response = await fetch(`${BASE_URL}/sepa/beneficiaries?${params}`, { headers });
if (!response.ok) throw new Error(`HTTP ${response.status}`);
const { beneficiaries } = await response.json();
const beneficiary = beneficiaries[0];
const beneficiaryId = beneficiary.id;
const beneficiaryName = beneficiary.name;
console.log(`Found beneficiary: ${beneficiaryName} (${beneficiaryId})`);
Verify the SEPA payee (VoP)
Before creating a transfer, you must verify the payee through Verification of Payee (VoP). This returns a proof_token that you will include in the transfer request.Endpoint: Verify a SEPA payeeOAuth scope required: payment.write
The proof_token is valid for 23 hours only. Always verify immediately before creating the transfer. Excessive verifications not followed by a transfer may be rate-limited.
response = requests.post(
f"{BASE_URL}/sepa/verify_payee",
headers={**headers, "Content-Type": "application/json"},
json={
"iban": "FR7616958000014849440866435",
"beneficiary_name": beneficiary_name,
},
)
response.raise_for_status()
data = response.json()
proof_token = data["proof_token"]["token"]
match_result = data["match_result"]
# match_result values:
# MATCH_RESULT_MATCH → proceed with confidence
# MATCH_RESULT_CLOSE_MATCH → consider reviewing the name
# MATCH_RESULT_NO_MATCH → caution, consider skipping transfer
# MATCH_RESULT_NOT_POSSIBLE → verification unavailable for this IBAN
print(f"Verification result: {match_result}")
const verifyResponse = await fetch(`${BASE_URL}/sepa/verify_payee`, {
method: "POST",
headers: { ...headers, "Content-Type": "application/json" },
body: JSON.stringify({
iban: "FR7616958000014849440866435",
beneficiary_name: beneficiaryName,
}),
});
if (!verifyResponse.ok) throw new Error(`HTTP ${verifyResponse.status}`);
const response = await verifyResponse.json();
const proofToken = response.proof_token.token;
const matchResult = response.match_result;
// match_result values:
// MATCH_RESULT_MATCH → proceed with confidence
// MATCH_RESULT_CLOSE_MATCH → consider reviewing the name
// MATCH_RESULT_NO_MATCH → caution, consider skipping transfer
// MATCH_RESULT_NOT_POSSIBLE → verification unavailable for this IBAN
console.log(`Verification result: ${matchResult}`);
Create the SEPA transfer
Submit the transfer using the beneficiary_id from Step 1 and the proof_token from Step 2.Endpoint: Create a SEPA transferOAuth scope required: payment.write
Do not forget to include the X-Qonto-Idempotency-Key header to prevent duplicate transfers in case of network errors. See Idempotent requests. Transfers above €30,000 require at least one attachment. Upload the attachment first via POST /v2/attachments and include its ID in the request. import uuid
response = requests.post(
f"{BASE_URL}/sepa/transfers",
headers={
**headers,
"Content-Type": "application/json",
"X-Qonto-Idempotency-Key": str(uuid.uuid4()),
},
json={
"vop_proof_token": proof_token,
"transfer": {
"bank_account_id": "{your_bank_account_id}",
"reference": "Invoice payment INV-001",
"amount": "1100.50",
"beneficiary_id": beneficiary_id,
# "scheduled_date": "2026-04-01", # Optional: ISO 8601 date
# "attachment_ids": ["uuid-1"], # Required if amount > €30,000
},
},
)
response.raise_for_status()
transfer = response.json()["transfer"]
print(f"Transfer created: {transfer['id']} — status: {transfer['status']}")
const { randomUUID } = await import("node:crypto");
const transferResponse = await fetch(`${BASE_URL}/sepa/transfers`, {
method: "POST",
headers: {
...headers,
"Content-Type": "application/json",
"X-Qonto-Idempotency-Key": randomUUID(),
},
body: JSON.stringify({
vop_proof_token: proofToken,
transfer: {
bank_account_id: "{your_bank_account_id}",
reference: "Invoice payment INV-001",
amount: "1100.50",
beneficiary_id: beneficiaryId,
// scheduled_date: "2026-04-01", // Optional: ISO 8601 date
// attachment_ids: ["uuid-1"], // Required if amount > €30,000
},
}),
});
if (!transferResponse.ok) throw new Error(`HTTP ${transferResponse.status}`);
const { transfer } = await transferResponse.json();
console.log(`Transfer created: ${transfer.id} — status: ${transfer.status}`);
Instant transfer limits: Instant transfers fall back to standard SEPA if the amount exceeds the limit. For trusted beneficiaries: €10,000 per transfer or €50,000 within 24h.
Example output:
[Step 1] GET /sepa/beneficiaries
Found beneficiary: Admin (0199cd7a-xxxx-xxxx-xxxx-xxxxxxxxxxxx)
[Step 2] POST /sepa/verify_payee
Verification result: MATCH_RESULT_MATCH
proof_token: 1|1|1773938795|MEUCI… (valid 23h)
[Step 3] POST /sepa/transfers
Transfer created: 019d06fd-xxxx-xxxx-xxxx-xxxxxxxxxxxx — status: pending
We now have a submitted SEPA transfer. The transfer id and status in the response confirm it has been accepted for processing.