Checkout API
Create a payment, redirect the customer, receive a webhook when the money has arrived. Three calls, no card forms, no PCI scope.
Developer portal
One REST API, ready-made shop plugins and a free sandbox. Accept Pay by Bank, QR payments and payment links – with the money on your account in seconds and a webhook that tells your system the moment it arrives.
Four steps from "Pay now" to money on your account. Your system only talks to us; we talk to the customer's bank.
One API call with the amount, your order number and the page to return to. You get a checkout link and a QR code back.
Online: your shop redirects to the checkout link. In store: the customer scans the QR code with their banking app or the Firstguard app.
The customer confirms with fingerprint or face ID. No card number, no form to fill in, nothing to type.
We call your server with the result (payment.paid). Ship the order – the money is already on your account.
Pick the building blocks you need. They share the same account, merchant panel and webhooks.
Create a payment, redirect the customer, receive a webhook when the money has arrived. Three calls, no card forms, no PCI scope.
Generate links from the merchant panel or programmatically for invoices, quotes, e-mails and chat. Single-use or reusable, with expiry dates.
Every payment has a QR code endpoint that returns a PNG. Show it on a screen, a kiosk, a receipt or a printed poster.
Official modules for WooCommerce, Magento and Shopware. Install, paste your API key, set the webhook, go live.
Full or partial refunds and daily settlement reports (CSV / JSON) through the same API and the merchant panel.
An isolated test environment with a simulated bank, so you can test every step – including rejected payments – before the first real euro.
Documentation
Getting started
The Firstguard API lets you accept account-to-account payments (Pay by Bank) in your shop, app or point of sale. Everything runs over HTTPS with JSON bodies. You authenticate every request with an API key, create payments with a single call and receive the outcome on a webhook – no polling required.
https://api.firstguardpayments.comhttps://api.sandbox.firstguardpayments.comhttps://merchant.firstguardpayments.comhttps://merchant.sandbox.firstguardpayments.comAll amounts are in euro with two decimals (48.00). Timestamps are ISO 8601 in UTC. Identifiers you receive from us (payment IDs, transaction IDs) are stable and safe to store.
Getting started
The sandbox is a complete copy of production where no real money moves. Use it to build and test your integration, then switch the two URLs to go live.
https://merchant.sandbox.firstguardpayments.com. When a one-time code is requested, use 123456.https://api.sandbox.firstguardpayments.com. Every endpoint of the reference is available there.Sandbox payments are limited to a maximum of €10 per payment. Sandbox and production are fully separated: keys, webhooks and data never cross over.
Getting started
API keys are created in the merchant panel. Sandbox keys start with fg_test_, production keys with fg_live_.
X-Api-Key header of every request.X-Api-Key: fg_test_9f1c2a7e4b8d4c0e9a3f6b2d1e5c7a9bKeep keys secret. Never put them in front-end code, mobile apps or public repositories. You can revoke a key at any time in the panel; create a separate key per system so you can rotate them independently.
Receive payments
You do not need a developer to get paid. Payment links and "Pay with Firstguard" buttons are generated in the merchant panel and work in e-mails, invoices, chats and on any website.
<a href="https://pay.firstguardpayments.com/l/inv-2026-0142"
class="fg-pay-button">Pay €48.00 with Firstguard</a>
<script src="https://pay.firstguardpayments.com/button.js" async></script>Receive payments
Send the amount, your own order reference and the URLs to return to. The response contains a redirectUrl for the customer, a paymentId for later look-ups and an expiry time (payments expire after 15 minutes by default).
curl -X POST https://api.sandbox.firstguardpayments.com/v1/payments \
-H "X-Api-Key: fg_test_your_key" \
-H "Content-Type: application/json" \
-d '{
"orderId": "order-1042",
"amount": 48.00,
"currency": "EUR",
"languageCode": "en",
"successUrl": "https://yourshop.com/thank-you",
"cancelUrl": "https://yourshop.com/cart",
"payerName": "Anna Schmidt"
}'const response = await fetch("https://api.sandbox.firstguardpayments.com/v1/payments", {
method: "POST",
headers: {
"X-Api-Key": process.env.FIRSTGUARD_API_KEY,
"Content-Type": "application/json",
},
body: JSON.stringify({
orderId: "order-1042",
amount: 48.00,
currency: "EUR",
languageCode: "en",
successUrl: "https://yourshop.com/thank-you",
cancelUrl: "https://yourshop.com/cart",
}),
});
const payment = await response.json();
// Send the customer to the checkout
res.redirect(payment.redirectUrl);<?php
$payload = [
'orderId' => 'order-1042',
'amount' => 48.00,
'currency' => 'EUR',
'languageCode' => 'en',
'successUrl' => 'https://yourshop.com/thank-you',
'cancelUrl' => 'https://yourshop.com/cart',
];
$ch = curl_init('https://api.sandbox.firstguardpayments.com/v1/payments');
curl_setopt_array($ch, [
CURLOPT_POST => true,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => [
'X-Api-Key: ' . getenv('FIRSTGUARD_API_KEY'),
'Content-Type: application/json',
],
CURLOPT_POSTFIELDS => json_encode($payload),
]);
$payment = json_decode(curl_exec($ch), true);
header('Location: ' . $payment['redirectUrl']);import os, requests
response = requests.post(
"https://api.sandbox.firstguardpayments.com/v1/payments",
headers={"X-Api-Key": os.environ["FIRSTGUARD_API_KEY"]},
json={
"orderId": "order-1042",
"amount": 48.00,
"currency": "EUR",
"languageCode": "en",
"successUrl": "https://yourshop.com/thank-you",
"cancelUrl": "https://yourshop.com/cart",
},
)
payment = response.json()
# Send the customer to the checkout
return redirect(payment["redirectUrl"])Response 201 Created
{
"paymentId": "pay_7Hk2mQ9xL4",
"transactionId": "b3b8f8e2-9c2e-4c1a-8e2d-3f7b8e2c9c2e",
"status": "initialized",
"redirectUrl": "https://pay.firstguardpayments.com/c/7Hk2mQ9xL4",
"qrCodeUrl": "https://api.sandbox.firstguardpayments.com/v1/payments/pay_7Hk2mQ9xL4/qr-code",
"expiresAt": "2026-09-02T14:36:07Z"
}After the customer has paid, they are sent back to your successUrl. Do not ship on the redirect alone – wait for the payment.paid webhook, or check the status with GET /v1/payments/{paymentId}.
Receive payments
Every payment has a QR code. Request it as a PNG and show it on a customer display, a tablet at the counter or a self-service kiosk. The customer scans it with their banking app or the Firstguard app and approves.
curl https://api.sandbox.firstguardpayments.com/v1/payments/pay_7Hk2mQ9xL4/qr-code?size=512 \
-H "X-Api-Key: fg_test_your_key" \
--output qr.pngThe QR code stays valid until the payment expires. For a counter that is always ready, create a new payment per transaction; for a fixed amount (tips, donations, a menu item) create a reusable payment link instead.
Webhooks
Webhooks are HTTP POST requests we send to your server whenever a payment changes status. Register an endpoint in the merchant panel under Settings → API → Webhooks (or with POST /v1/webhooks), choose the events you want and save. We retry failed deliveries with increasing intervals for up to 24 hours.
| Event | When it is sent |
|---|---|
payment.initialized | The payment was created and is waiting for the customer. |
payment.pending | The customer has approved; the bank is processing the transfer. |
payment.paid | The money has arrived on your account. Safe to ship. |
payment.rejected | The customer cancelled or the bank declined. |
payment.errored | A technical error occurred; the customer can try again. |
payment.refunded | A refund you issued was executed. |
{
"event": "payment.paid",
"paymentId": "pay_7Hk2mQ9xL4",
"transactionId": "b3b8f8e2-9c2e-4c1a-8e2d-3f7b8e2c9c2e",
"orderId": "order-1042",
"amount": 48.00,
"currency": "EUR",
"status": "paid",
"statusMessage": "Accepted by bank",
"date": "2026-09-02T14:21:07Z"
}Respond with HTTP 200 within 10 seconds. Handle events idempotently: the same event can be delivered more than once, so use paymentId and status to decide whether you have processed it already.
Webhooks
Every webhook carries an X-Signature header: an HMAC-SHA256 of the raw request body, created with the secret key shown next to your webhook in the merchant panel. Verify it before you trust the payload.
const crypto = require("crypto");
const express = require("express");
const app = express();
app.post("/webhooks/firstguard", express.raw({ type: "application/json" }), (req, res) => {
const expected = crypto
.createHmac("sha256", process.env.FIRSTGUARD_WEBHOOK_SECRET)
.update(req.body)
.digest("hex");
const received = req.headers["x-signature"] || "";
if (!crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(received))) {
return res.status(400).send("invalid signature");
}
const event = JSON.parse(req.body);
if (event.status === "paid") {
markOrderAsPaid(event.orderId); // your code
}
res.sendStatus(200);
});<?php
$body = file_get_contents('php://input');
$received = $_SERVER['HTTP_X_SIGNATURE'] ?? '';
$expected = hash_hmac('sha256', $body, getenv('FIRSTGUARD_WEBHOOK_SECRET'));
if (!hash_equals($expected, $received)) {
http_response_code(400);
exit('invalid signature');
}
$event = json_decode($body, true);
if ($event['status'] === 'paid') {
mark_order_as_paid($event['orderId']); // your code
}
http_response_code(200);Plugins
Adds "Firstguard – Pay by Bank" as a payment method to the WooCommerce checkout. Orders are marked as paid automatically through the webhook.
https://yourshop.com/?wc-api=firstguard) into the merchant panel and paste the secret key back into the plugin.Plugins
bin/magento setup:upgrade and bin/magento cache:flush.https://yourshop.com/firstguard/webhook in the merchant panel and paste the secret key into the module configuration.Plugins
https://yourshop.com/firstguard/webhook in the merchant panel and paste the secret key into the plugin configuration.Reference
Errors use standard HTTP status codes with a JSON body that explains what went wrong:
{
"error": {
"code": "validation_error",
"message": "amount must be greater than 0",
"field": "amount"
}
}| Status | Meaning |
|---|---|
400 | Invalid request – see error.field. |
401 | Missing or invalid API key. |
404 | Payment or webhook not found. |
409 | Duplicate Idempotency-Key with a different body. |
429 | Rate limit reached (600 requests per minute per key). Retry after the Retry-After header. |
5xx | Temporary problem on our side – safe to retry with the same idempotency key. |
Send an Idempotency-Key header (any unique string, for example your order number) with POST requests. If a request times out, repeat it with the same key and you will never create a payment twice.
API reference
Base URL https://api.firstguardpayments.com · Authentication X-Api-Key header · JSON in, JSON out
/v1/paymentsCreate a payment and receive a checkout link and QR code./v1/payments/{paymentId}Retrieve the current status and details of a payment./v1/payments/{paymentId}/qr-codePNG image of the payment QR code (size 128–1024 px)./v1/payments/{paymentId}/pay-urlFresh checkout URL for an open payment, optionally with a new returnUrl./v1/payments/{paymentId}/refundsRefund a paid payment fully or partially (amount optional)./v1/paymentsList payments, filter by status, from and to. Paginated./v1/payment-linksCreate a reusable or single-use payment link./v1/payment-links/{linkId}Retrieve a link and the payments made through it./v1/payment-links/{linkId}Deactivate a link./v1/webhooksRegister a webhook endpoint; returns the signing secret./v1/webhooksList registered endpoints./v1/webhooks/{webhookId}Remove an endpoint./v1/settlementsDaily settlement reports (JSON or CSV with Accept: text/csv)./v1/paymentsCreate a paymentX-Api-Key requiredIdempotency-Key optionalorderId string requiredamount number required48.00. Minimum 0.01.currency stringEUR.languageCode stringen, de, fr, it. Default en.successUrl string requiredcancelUrl string requiredpayerName stringdescription stringexpiresIn integerpaymentId stringtransactionId stringstatus stringinitialized, pending, paid, rejected, errored, expired, refunded.redirectUrl stringqrCodeUrl stringexpiresAt stringTLS 1.2+ on every connection, API keys you can rotate, HMAC-signed webhooks.
Payments run over PSD2 open-banking connections to the customer's own bank.
All data is processed and stored in European data centres under GDPR.
Integration questions are answered by engineers within one business day. Contact support
Create a free sandbox account, generate your API keys and make your first test payment in minutes. Going live is a change of two URLs.