Checkout
This page details how the checkout flow works. It allows you to make a payer pay for a payee's item and initiate a transaction between the two. It assumes that you already have credentials to successfully call Safepay Nordic APIs and have subscribed to webhooks.
Both the payer and the payee must be connected with Safepay Nordic, so you can identify them by their safepayAccountId in payerId and payeeId.
How it works
- When a user wants to buy an item on your service, your backend creates a transaction.
- Your backend creates a payment for the transaction.
- Your backend chooses the payment method, preferably one of the payer's bank accounts, and gets a
paymentUrl. - You redirect the payer to the
paymentUrl. - The payer pays the amount into Safepay Nordic's escrow account and is sent back to your
returnUrl. - Your backend receives a
transaction.payment.paidwebhook.
If the payer can't pay with PSD2, they can pay by bank transfer instead.
Create transaction
curl https://sandbox-api.safepaynordic.dk/v2/transactions/marketplace \
-X POST \
-H 'Authorization: Bearer {API_TOKEN}' \
-H 'Content-Type: application/json' \
-H 'Idempotency-Key: 81784a78-8b7f-4e49-ad4e-383417c970cf' \
-d '{
"title": "Adidas Campus 00s",
"description": "Almost new Adidas Campus 00s. Used 3 times and still have the box and receipt.",
"amount": 1200.50,
"currency": "DKK",
"payeeId": "82f700bd-5789-4623-95e7-0d34fde4720c",
"payerId": "003dc3d2-dc30-4b37-a573-f664a03f5154",
"payeeFixedFee": 15,
"payerFixedFee": 10,
"referenceId": "your-reference-id",
"imageUrls": [ "https://domain.com/public-image-url-1.jpg" ]
}'
{
"transactionId": "295a2546-ee78-4ec4-9e0f-b49af13536b8"
}
Use the transactionId to create a payment.
Parameters
- title: Required. Shown to the parties during checkout.
- amount: Required. Rounded to 2 decimals. It must cover the payee's fees.
- payerId: Required. The payer's
safepayAccountId. - payeeId: Required. The payee's
safepayAccountId. - currency: Optional.
DKK(default) orEUR. - description: Optional. Free-text description of the transaction.
- imageUrls: Optional. Public URLs of images of the item.
- payeeFixedFee / payerFixedFee: Optional. Your fee, charged to the payee and the payer respectively. Together they make up your fee for the transaction, which Safepay Nordic's fee is taken from. If your fee doesn't cover Safepay Nordic's fee, the difference is billed to you.
- referenceId: Optional. Your own reference for the transaction. It's returned on the transaction and in webhooks, and can be used to filter when listing transactions.
- expectedCompletion: Optional. When the transaction is expected to complete. Must be in the future.
- documentId: Optional. Connects an existing document to the transaction.
Retrying safely
Send an Idempotency-Key header, as in the example above, so a retried request can't create a second transaction. See Idempotency.
Errors
All errors are returned as 400 Bad Request with one of these titles:
Invalid payee:payeeIdis missing.Invalid amount: theamountdoesn't cover the fees.Invalid document: thedocumentIdwasn't found.
Create payment
Create a payment for the transaction.
curl https://sandbox-api.safepaynordic.dk/v2/payments \
-X POST \
-H 'Authorization: Bearer {API_TOKEN}' \
-H 'Content-Type: application/json' \
-d '{
"transactionId": "295a2546-ee78-4ec4-9e0f-b49af13536b8",
"safepayAccountId": "003dc3d2-dc30-4b37-a573-f664a03f5154",
"returnUrl": "https://marketplace.dk/orders/1234"
}'
{
"paymentId": "7c1e9a3d-2f4b-4d8e-a6c5-0b9f8e7d6a51",
"paymentUrl": "https://sandbox.mitsafepay.dk/l/p7c1e9a3d"
}
Next, choose the payment method to get the paymentUrl to send the payer to. The paymentUrl returned here shows a screen where the payer chooses how to pay; only use it if you can't choose the payment method yourself.
Each call creates a new payment, so send an Idempotency-Key header to retry safely, as described in Idempotency.
Parameters
- transactionId: Required. The transaction to pay for.
- safepayAccountId: Required. The
safepayAccountIdof the user who pays, usually the transaction's payer. Must be connected to you. - amount: Optional. Defaults to the remaining amount to pay, including the payer's fees. Must be greater than zero and can't exceed the remaining amount.
- description: Optional. Defaults to the transaction's Safepay ID.
- returnUrl: Optional. Where the payer is sent after the payment.
- skipUI: Optional. When
true, the payer is redirected straight to yourreturnUrlafter the payment, without seeing the Safepay Nordic status screen. Only applies whenreturnUrlis set.
Errors
400 Invalid amount: theamountis zero or negative, or higher than the remaining amount.404 Not Found: the transaction wasn't found, or thesafepayAccountIddoesn't exist or isn't connected to you.
Choose payment method
Choose how the payer pays and get the paymentUrl to send them to. We recommend PSD2 with one of the payer's bank accounts, so the payer goes straight to approving the payment in their bank.
curl https://sandbox-api.safepaynordic.dk/v2/payments/{paymentId}/initialize \
-X POST \
-H 'Authorization: Bearer {API_TOKEN}' \
-H 'Content-Type: application/json' \
-d '{
"paymentMethod": "PSD2",
"bankAccountId": "a1b2c3d4-5e6f-4a7b-8c9d-0e1f2a3b4c5d"
}'
{
"paymentUrl": "https://sandbox.mitsafepay.dk/l/p7c1e9a3d/psd2/a1b2c3d4-5e6f-4a7b-8c9d-0e1f2a3b4c5d"
}
- paymentMethod: One of:
PSD2(recommended): pay from one of the payer's bank accounts, given bybankAccountIdfrom List bank accounts.BANK: pay from an account in the bank given bybankId, abankIdfrom List banks.VIPPSMOBILEPAY: pay with Vipps.
- bankAccountId: Required for
PSD2. Must be one of the paying user's own bank accounts. - bankId: Required for
BANK.
If paymentMethod is omitted, the returned paymentUrl shows the payment method selection screen, as with the paymentUrl from Create payment. A missing bankAccountId or a missing or invalid bankId returns 400. A bankAccountId that isn't one of the paying user's bank accounts returns 404 Bank account not found, and an unknown paymentId returns 404.
Pay by bank transfer
If the payer can't pay with PSD2, they can pay with an ordinary bank transfer into Safepay Nordic's escrow account. Get the details to show the payer:
curl https://sandbox-api.safepaynordic.dk/v2/transactions/{transactionId}/payment-instructions/{safepayAccountId} \
-H 'Authorization: Bearer {API_TOKEN}'
{
"amount": 1210.50,
"currency": "DKK",
"bankMessage": "DT-1A2B3C4D",
"bban": "{ESCROW_BBAN}",
"iban": "{ESCROW_IBAN}",
"swift": "JYBADKKK",
"fik": "{FIK_CODE}"
}
- transactionId: The transaction to pay for.
- safepayAccountId: The
safepayAccountIdof the user who pays, usually the transaction's payer. Must be connected to you. Money paid this way is attributed to this user, and any refund is paid back to them.
You don't need to create a payment first.
Response
- amount: What's left to pay, including the payer's fees, minus what has already been paid. Ask again after a partial payment to get the new amount.
- currency:
DKKorEUR, the transaction's currency. - fik: A FIK code (Fælles indbetalingskort, also called FI-kort) for the transfer. Only card type
+71is supported. Show the code to the payer exactly as returned. Empty forEUR. - bban / iban / swift: Safepay Nordic's escrow account, for a transfer to an account number.
- bankMessage: The reference the payer must put in the transfer's message when paying to the account number.
How the transfer is matched
- FIK (recommended for
DKK): the transfer is matched automatically when it shows up on Safepay Nordic's bank account, and you receive thetransaction.payment.paidwebhook. - Account number with
bankMessage(always the case forEUR): the transfer is matched by Safepay Nordic, and you receive thetransaction.payment.paidwebhook once it's matched. A transfer without thebankMessageor with the wrong one can't be matched to the transaction, which delays it.
Transfers older than 30 days aren't matched automatically and are handled by Safepay Nordic.
A bank transfer doesn't confirm the payment while the payer is on your site. Show the payer the instructions, and wait for the webhook before you treat the transaction as paid.
Errors
404 Not Found: the transaction wasn't found, or thesafepayAccountIddoesn't exist or isn't connected to you.
After payment
When the payment is done, the payer is sent to your returnUrl: directly if you set skipUI, otherwise via a Safepay Nordic status screen with a button back to you. If you didn't set a returnUrl, the payer stays on the status screen.
No payment status is added to the returnUrl, and the payer is also sent there if the payment failed. Confirm the state server-side using the webhooks below or by calling GET /v2/transactions/{transactionId} (see Transaction details).
Webhooks
Safepay Nordic sends a webhook when the transaction changes.
{
"eventName": "transaction.payment.paid",
"eventDate": "2026-01-01T12:00:00+01:00",
"webhookId": "ed3325d8-b379-444a-9cc1-7d119efefbea",
"transactionId": "295a2546-ee78-4ec4-9e0f-b49af13536b8",
"paymentId": "7c1e9a3d-2f4b-4d8e-a6c5-0b9f8e7d6a51",
"referenceId": "your-reference-id",
"payee": {
"safepayAccountId": "82f700bd-5789-4623-95e7-0d34fde4720c",
"referenceId": "your-payee-reference-id"
},
"payer": {
"safepayAccountId": "003dc3d2-dc30-4b37-a573-f664a03f5154",
"referenceId": "your-payer-reference-id"
}
}
paymentId is only set for the payment events.
| eventName | Sent when |
|---|---|
transaction.payment.paid | A payment from the payer into escrow has been authorized. |
transaction.payment.failed | A payment from the payer failed. |
transaction.succeeded | The transaction completed and the money is released to the payee. |
transaction.cancelled | The transaction was cancelled. |
Next steps
When the trade is completed or falls through, release the payout or cancel the transaction as described in Complete transaction. Use Transaction details to read the transaction.