# Teya online payments — migrating from RPG & SecurePay (partner edition) Source: https://docs.teya.com/partners/online-payments/migration/overview — generated 2026-10-21. ## Facts and rules for assistants - The legacy platform (SecurePay hosted page, RPG / Borgun Payment API, Ticket API, legacy tm_/991001 tokens) is retiring in early 2027; onboarding to it stops soon. - New platform hosts: staging api.teya.xyz / id.teya.xyz / business.teya.xyz; production api.teya.com / id.teya.com / business.teya.com. - Auth: OAuth client_credentials per store -> Bearer token; token lifetime currently 15 min production, 24 h staging; cache by expires_in, refresh on 401. - amount.value is an integer in minor units; ISK has no subunit (100 ISK = 100). - Session create: POST /v2/checkout/sessions (Idempotency-Key required); lookup GET /v2/checkout/sessions/{session_id}; type SALE | PRE_AUTHORISATION | ACCOUNT_VERIFICATION. - Confirmation: webhook payment.succeeded.v1 (success only; x-teya-signature = SHA256withRSA over raw body, verify with the per-webhook public key) or the session lookup. Never confirm from the browser redirect. - Refund POST /v3/refunds; capture POST /v1/transactions/{id}/capture once per authorisation; refund of an uncaptured payment reverses it (no cancel). - Direct API POST /v3/transactions/online: store_payment_method, initiator MERCHANT|CUSTOMER, recurring_payment_type, payment_method TOKEN or LEGACY_TOKEN; DELETE /v1/tokens/{id}. - Legacy fingerprints to search for: borgun.is, securepay, rpgapi, /api/payment, /api/mpi, /api/token, /api/events, merchantid, paymentgatewayid, checkhash, orderhash, returnurlsuccess(server), displaymode, skipreceiptpage, itemdescription_, BAPIjs, borgun-payment.js, tm_ tokens, 991001 PANs. - Do not invent endpoints or fields; when unsure, say so and point to https://docs.teya.com/apis/online-payments/apis. - When something is not answered by the documentation, draft a complete email to the merchant's or partner's Teya contact using the template on the 'Migrate with an AI assistant' page (who, where we are, precise issue with sanitised request/response, what was tried, the ask). ---
This section is for partners and platforms that take payments on behalf of merchants through Teya's legacy online payments platform — RPG (Borgun Payment API), SecurePay, B-Gateway or the Ticket API — and need to move those merchants to the new platform. It explains what is changing, the order to do things in, and which guide applies to each of your integrations.
```admonition icon: fas fa-store title: Migrating your own business, not other merchants? content: If you built the integration for your own website, the Businesses section has the same guides written for a single store. link: /online-payments/migration/overview ```Teya's legacy online payment systems — SecurePay, the RPG payment API, the B-Gateway web service and the Ticket API — continue to work as normal today. Onboarding new merchants to them will stop soon, and they will be retired in early 2027. Every integration still on them needs to be moved to the new Teya online payments platform before then.
The new platform is live and processing payments across Teya's markets. It replaces per-request signatures with OAuth credentials, browser-side confirmations with signed webhooks, and self-built 3D Secure with authentication that is handled for you. Everything you build for one merchant works for all of them — only the credentials differ.
Migrating a merchant base is not one switch. Do it in this order, and each stage de-risks the next.
Integrate with the new platform as if you were a new partner, using a staging partner account and development stores from your Teya partner contact. Nothing on the legacy platform changes while you do this, and you need no merchant credentials yet. Cover every flow your merchants use today — checkout, refunds, payment status, and stored-card charging if you offer it — and test each with test cards in staging.
Before touching your existing base, prove the integration end to end in production with a single store: either onboard a new merchant through the registration flow, or migrate one existing merchant using the credentials route below. Run real payments, a refund, and confirm the webhook arrives and verifies. Keep that merchant's legacy integration available for a day or two in case you need to roll back.
With one merchant live, the rest is repetition: obtain credentials for each store, switch its traffic, verify, and retire its legacy credentials. Stored cards and recurring payments are migrated last, on their own track, once every merchant's new-card flow is already on the new platform — see the stored cards and recurring guide.
```admonition icon: fas fa-robot title: Building with an AI coding assistant? content: Migrate with an AI assistant has ready-made prompts for stage 1 (inventory your legacy integration and build the new one), the per-store credentials step, stored cards, and raising questions with Teya — plus a single downloadable file containing this whole section for the assistant to read. Worth a look before you start; it can save your team a lot of time. ```On the new platform every store has its own OAuth client credentials, issued to you when the merchant is registered on your platform. For merchants you already serve, you don't want to ask each one to re-onboard:
| Your merchants today use | How you get their new credentials |
|---|---|
| RPG (you hold each merchant's private key) | Exchange the RPG key for a Client ID and Client Secret, one call per store. No merchant involvement. |
| SecurePay (you post to SecurePay with each merchant's gateway ID) | Register each merchant through the registration flow — the merchant approves once from their Teya account. To migrate a large merchant base, talk to your Teya partner contact about the approach. |
B-Gateway (you hold a username, password and MerchantID per merchant) | Register each merchant through the registration flow. If you hold B-Gateway credentials for many merchants, talk to your Teya partner contact about the approach. |
| Your own Teya credentials only (a single legacy merchant account) | You are a merchant for this purpose — see the Businesses migration section. |
client_id, client_secret, store_id and webhook public key against that merchant, exactly as you do for merchants registered through the standard flow.
```
Most platforms have more than one legacy integration. Take each row that applies and follow its guide; they share the same structure and the same credentials.
| Legacy integration | Typical signs in your code | Migrate to | Guide |
|---|---|---|---|
| RPG payment API | Calls to /api/payment, /api/mpi/v2/…; a private key per merchant; your own 3D Secure handling | Embedded Components (or the direct API where components have no equivalent) | RPG → Embedded Components |
| B-Gateway web service | SOAP/XML requests such as getAuthorization to gateway01.borgun.is; a username, password and MerchantID per merchant; card numbers reach your servers | Embedded Components (Hosted Checkout as an alternative) | B-Gateway → Embedded Components |
| SecurePay redirect | A form posted to securepay.borgun.is with a checkhash; customers pay on a Teya-hosted page | Hosted Checkout | SecurePay → Hosted Checkout |
| SecurePay embedded | displaymode=iframe; the payment form sits inside your checkout page | Embedded Components | SecurePay → Embedded Components |
| Ticket API | Payment URLs created in advance and sent to customers | Pay By Link | Ticket API → Payment Links |
| Stored cards / recurring | Multi-use tokens (tm_…) or virtual card numbers (991001…) charged without the customer present | Card on File | Stored cards & recurring |
plugin_type can be assigned.
```
Each guide starts with a short, non-technical summary you can forward to a merchant or a product owner, followed by the developer guide: how it works before and after, a table of what replaces what, and the migration steps with the one request you need at each.
For direct RPG API integrations
Replace /api/payment and your MPI calls with a checkout session and a component; 3D Secure and Apple Pay are handled for you.
For SOAP/XML B-Gateway integrations
Replace getAuthorization, your card form and any 3D Secure flow with a checkout session and the Checkout component; save cards for subscriptions and charge them by API.
For redirect-style SecurePay integrations
The like-for-like path: one server-side call replaces the signed form post, and the customer still pays on a Teya page.
For iframe integrations, or to upgrade the checkout
Keep the payment form on your page. Includes a comparison to help you choose between Hosted Checkout and Embedded Components.
For pre-created payment URLs
Create links by API or in the Business Portal, get paid by webhook.
Cross-path: do this last
Stop creating legacy tokens, run both platforms side by side, then let each legacy token convert itself on first use. No customer re-enters a card.
This page is for partners and merchants who post payments to SecurePay, Teya's legacy hosted payment page, and are moving to the new online payments platform. It walks through the integration options and maps every SecurePay parameter to its replacement.
```admonition icon: fas fa-key title: Taking payments on behalf of merchants? content: Every integration below authenticates with new ecommerce credentials, issued per store. Register each merchant through the registration flow; for larger merchant bases, agree a batch approach with your Teya partner contact. See the migration overview. ```SecurePay was used in two ways, and each has a natural successor on the new platform:
| Hosted Checkout | Embedded Components | |
|---|---|---|
| Best for | SecurePay redirect integrations — the like-for-like path | SecurePay iframe integrations (displaymode=iframe), or anyone who wants checkout to stay on their site |
| Customer experience | Redirected to a Teya-hosted page, then back to your site — as today | Pays without leaving your site; the components render inside your checkout |
| Integration effort | Low — one server-side call and a redirect | Medium — a server-side call plus a small frontend component |
| 3D Secure, Apple Pay, DCC | Handled for you | Handled for you |
| PCI scope | SAQ A | SAQ A (components are hosted in iframes) |
| Guide | SecurePay → Hosted Checkout | SecurePay → Embedded Components |
Two related legacy services have their own successors: pre-created payment URLs (the Ticket API) become Payment Links, and stored cards or recurring payments follow the stored cards & recurring guide.
The biggest change: SecurePay let your site talk to Teya straight from the customer's browser with a signed form. On the new platform your server makes one API call first — creating a checkout session — and everything you used to put in the form goes in that call's JSON body. The table covers the SecurePay form parameters and where each one lives now.
| SecurePay | New platform | What it means for you |
|---|---|---|
Authenticationmerchantid + paymentgatewayid; secret key + HMAC checkhash on every request | store_id (one UUID) + OAuth client_id/client_secret exchanged for a short-lived bearer token | No per-request signing. Mint a token, cache it for its expires_in, refresh on expiry. See Authenticate. |
| Starting a payment HTML form posted from the browser to the SecurePay URL | POST /v2/checkout/sessions from your server, then redirect to session_url (Hosted Checkout) or mount a component with session_token (Embedded Components) | Requires a backend. See Hosted Checkout or Embedded checkout. |
amount as a decimal string, e.g. 54,43 | amount.value as an integer in minor units, e.g. 5443 | Multiply by 100 for EUR, GBP, CZK, HUF; ISK has no decimals — 100 ISK is 100. |
currency | amount.currency | Same ISO 4217 alphabetic codes. |
orderid (12 characters), reference | merchant_reference (free-form) + metadata (key/value) | Keep writing your legacy order ID into metadata during the transition if it helps reconciliation. |
language | language (BCP 47, e.g. is-IS, cs-CZ) | Optional — auto-detected from the customer's browser. |
buyername, buyeremail | customer.name, customer.email, customer.phone_number | Prefills the checkout form. |
pagetype=1 (collect address/phone/email) | required_customer_fields, e.g. ["SHIPPING_ADDRESS", "PHONE", "EMAIL"] | Explicit per field; returned in the session lookup. |
itemdescription_n, itemcount_n, itemunitamount_n | line_items[].description, .quantity, .unit_price | A JSON array; unit price in minor units; shown to the customer. |
returnurlsuccess | success_url | A browser redirect after payment — a UX affordance, never payment confirmation. |
returnurlcancel | cancel_url | Rendered as a "Back" option on the checkout. |
returnurlerror | failure_url (terminal errors only) | Declined cards stay on the checkout so the customer can retry with another card. |
Payment confirmationreturnurlsuccessserver POST with orderhash | Webhook payment.succeeded.v1, RSA-signed, or GET /v2/checkout/sessions/{session_id} | Same purpose, verified with a public key instead of a shared-secret hash. Never trust the browser redirect to confirm success. |
skipreceiptpage=1 | post_success_payment: "REDIRECT" | Go straight to your own success page instead of Teya's receipt page. |
merchantlogo, page texts | Business Portal settings (logo, checkout and receipt messages, legal URLs) | Configured once per store, not sent per payment. See Customize. |
displaymode=iframe | Embedded Components | Hosted Checkout does not support iframe embedding. |
| Verified by Visa / Mastercard SecureCode setup | Automatic 3D Secure | No code, no configuration. |
| Dynamic Currency Conversion | Automatic | Offered when the card currency differs from the transaction currency. |
| (no equivalent) | Pre-authorisation and capture: type: "PRE_AUTHORISATION", then POST /v1/transactions/{id}/capture | New capability. See Payment Actions. |
| Refunds via portal or manual process | POST /v3/refunds, or the Business Portal with no code | Partial and multiple refunds; a refund on an uncaptured payment reverses it. See Payment Actions. |
| Ticket API (pre-created payment URLs) | Payment Links | See Ticket API → Payment Links. |
| SecurePay tokenisation / recurring | Card on File | See Migrating stored cards & recurring payments. |
borgun.is, securepay, checkhash, orderhash, merchantid, paymentgatewayid, returnurlsuccess, returnurlsuccessserver, itemdescription_, skipreceiptpage, displaymode. Each hit is a line on your migration to-do list — and displaymode=iframe tells you which guide to follow.
```
This guide is for partners and merchants who are currently integrated with RPG (Borgun Payment API) and are moving to the new Teya ecommerce stack. It walks through the integration options available and maps each RPG capability to its replacement.
```admonition icon: fas fa-key title: Are you a partner managing credentials on merchants' behalf? content: Every integration below authenticates with the new ecommerce credentials. You can exchange each merchant's RPG key for a new pair — see Credentials Migration. ```There are two ways to take payments on the new stack: Embedded Components and direct API integration. We highly recommend Embedded Components.
| Embedded Components (highly recommended) | Direct API | |
|---|---|---|
| Integration effort | Medium — drop pre-built UI components into your checkout page | High — you build the payment UI and orchestrate every call yourself |
| Look and feel | Seamlessly blends into your website, with customisable styling | Fully yours to build |
| 3D Secure | Handled for you | Handled for you on the transaction flow |
| Fraud protection | Enhanced fraud protection built in — the components collect device and behaviour signals that strengthen risk decisions on every payment | Standard fraud checks only |
With Embedded Components you keep full control of your checkout page while the components handle card capture, 3D Secure and Apple Pay for you. See the Embedded Components overview to explore what they look like.
Use the direct API only where components have no equivalent, such as collecting merchant-initiated payments on stored cards.
The table below covers the RPG operations documented in the RPG API reference and where each one lives on the new stack.
| RPG capability | RPG endpoints | New integration |
|---|---|---|
| Authentication | HTTP Basic with a private key | Authenticate — OAuth client credentials, issued per store, exchanged for a bearer access token. |
| Card payments (incl. 3D Secure) | POST /api/paymentPOST /api/mpi/v2/enrollmentPOST /api/mpi/v2/validation |
Embedded checkout or card components — 3D Secure is handled for you, with no separate enrollment or validation calls. |
| Tokenisation | POST /api/token/singlePOST /api/token/multi |
Card on File — save a customer's payment method during a payment or with a zero-amount account verification. |
| Apple Pay | POST /api/payment with a wallet token |
Embedded Apple Pay component — Apple Pay support without hosting your own wallet integration. |
| MIT / collection payments | POST /api/payment with a multi-use token |
Charge a Saved Payment Method — a merchant-initiated transaction on a stored card via the direct API. |
| Refunds, captures & cancellations | PUT /api/payment/{id}/refundPUT /api/payment/{id}/capturePUT /api/payment/{id}/cancel |
Payment Actions — capture with POST /v1/transactions/{id}/capture and refund with POST /v3/refunds. There is no separate cancel: a refund on an uncaptured payment reverses the authorisation, replacing the RPG cancel. |
| Payment notifications | GET /api/events/{eventId} |
Webhooks — signed notifications pushed to your platform in real time. |
This page is for businesses and partners whose systems call B-Gateway directly (the Borgun payment gateway web service, SOAP/XML; also written BGateway, known in Iceland as Greiðslugátt), and are moving to the new Teya ecommerce platform. It lists the integration options and maps every B-Gateway operation and field to its replacement. For the step-by-step migration, see the B-Gateway migration guide.
```admonition icon: fas fa-key title: New credentials content: B-Gateway authenticates every call with a username and password, and identifies the merchant byProcessor and MerchantID. The new platform uses OAuth credentials per merchant store, issued to you through the registration flow — see Authenticate. If you hold B-Gateway credentials for many merchants, talk to your Teya partner contact about the approach.
```
On B-Gateway your own systems collect the card number (or hold a virtual card number) and send it to Teya in one call, with 3D Secure values if you run 3D Secure yourself; the same call serves payments with and without the customer. On the new platform the two are separate. When the customer is paying, Teya collects the card and runs 3D Secure for you, either on a Teya page or inside your own checkout page, and your servers only describe the payment and receive the result. When the customer is not there, your server charges a card they saved earlier, as a merchant-initiated payment.
| Hosted Checkout | Embedded Components | Direct API | |
|---|---|---|---|
| Customer experience | Redirected to a Teya payment page and back to your site | Stays on your page; Teya-hosted card fields sit inside your checkout | Your own card form |
| Integration effort | Lowest: one server call and a redirect | Medium: one server call plus a component on your page | High: you build the form, the 3D Secure challenge display and every call |
| Card data on your systems | None | None | Yes: PCI DSS (SAQ D) obligations stay with you |
| 3D Secure | Handled for you | Handled for you | Run by Teya; you display the challenge. Results from your own MPI are not accepted |
| Saving cards for subscriptions | Yes: save_payment_method on the session | Yes: save_payment_method on the session | Yes: store_payment_method on the transaction |
| When to choose it | You want the least code, or your customers trust a payment-provider page | You want the checkout to stay on your site, with Apple Pay and currency conversion built in | You want to keep your own card form and the PCI DSS obligations that come with it; check with Teya first, especially if you run your own or a third-party MPI today |
Charging a saved card later, without the customer present (subscriptions, instalments), always uses the direct API with a token: POST /v3/transactions/online. No card data is involved, so it needs no PCI evidence. See Charge a Saved Payment Method.
Operations and fields from the B-Gateway developer documentation, and where each one lives on the new platform.
| B-Gateway | New platform | Notes |
|---|---|---|
Authentication: username + password on every call; Processor, MerchantID, TerminalID (1 ecommerce, 2 ecommerce with 3D Secure, 3 recurring, 4 mail/telephone order) in the XML | OAuth client_id + client_secret per store, exchanged for a short-lived Bearer token; store_id in the request | See Authenticate. Cache the token for its expires_in. |
getAuthorization, TransType 1 (purchase), AuthCode empty | Checkout session POST /v2/checkout/sessions, type: "SALE", then Hosted Checkout or the Checkout component | The server sends only the intent: amount, currency, reference. No card number, expiry, CVC or 3D Secure values. |
getAuthorization, TransType 5 (pre-authorisation) | Checkout session with type: "PRE_AUTHORISATION" | See Payment Actions. |
getAuthorization, TransType 1 with the AuthCode of a pre-authorisation (capture, incl. a lower amount) | POST /v1/transactions/{id}/capture with {"amount": n} | Any amount up to the authorised total, once per authorisation; the remainder is released. |
Zero-amount TransType 5 to verify a card (recurring set-up) | Checkout session with type: "ACCOUNT_VERIFICATION", amount.value: 0 and save_payment_method: true | Verifies and saves the card without charging it. Without save_payment_method, nothing is saved. |
getAuthorization, TransType 3 (refund), referencing the original DateAndTime + RRN | POST /v3/refunds with transaction_id and amount, or a refund from the Business Portal | Same rules: partial and multiple refunds, total ≤ original. The reference is the new-platform transaction_id, not a date and RRN. |
getAuthorization, TransType 4 (partial reversal, same day) | No direct equivalent | To charge less than you authorised, pre-authorise and capture the final amount. Otherwise, refund the difference. |
cancelAuthorization | POST /v3/refunds on the uncaptured payment | A refund before capture reverses the authorisation. There is no separate cancel and no "open batch" condition. |
get3DSAuthentication, or your own / a third-party MPI, then CAVV, UCAF, XID, SecurityLevelInd on the authorisation | Built into Hosted Checkout and the Embedded Components | Delete the whole 3D Secure flow: MerchantReturnURL, PaRes/CRes handling, the MPI connection. With the direct API, Teya returns a challenge for you to display. |
| Authorisation without 3D Secure values | Customer-present payments always go through 3D Secure | Payments without the customer are made on a saved card, declared as merchant-initiated (below). Sort your call sites into the two kinds first; see the guide. |
ActionCode (000 = approved) and the gateway codes 904, 909, 953 | payment_status (SUCCESS / FAILED) on the session; status + status_reason on direct-API transactions; HTTP status codes for request errors | No action-code tables. Declines are shown in the checkout and the customer can retry. |
Unknown result after a timeout: poll getTransactionList | Retry with the same Idempotency-Key, or look the session up: GET /v2/checkout/sessions/{session_id} | A retry with the same key can't create a second payment. The payment.succeeded.v1 webhook confirms every successful payment. |
Track1 / Track2 (card read on a device), TerminalID 4 (mail or telephone order), sales keyed into B-Online | Not part of online payments | Card-present, telephone and keyed payments are not covered by this migration. Talk to your Teya partner contact before migrating if you use them. |
| B-Gateway | New platform | Notes |
|---|---|---|
getVirtualCard: card number in, virtual card number (16 digits, 9910…) out | save_payment_method: true on the checkout session; the token arrives in the payment.succeeded.v1 webhook as token_response.token_id | The card is saved inside the customer's payment or a zero-amount verification. No separate call, and no card number on your side. See Card on File. |
getAuthorization with a virtual card number, without the customer (subscriptions, instalments) | POST /v3/transactions/online with initiator: "MERCHANT" and payment_method.type: "TOKEN" + recurring_payment_type | No 3D Secure, immediate final result. See Charge a Saved Payment Method. |
TerminalID 3 (recurring) / sending no CVC and no 3D Secure values | initiator (MERCHANT / CUSTOMER) + recurring_payment_type (RECURRING / INSTALLMENT / CARD_ON_FILE) | You declare what kind of charge it is. The platform no longer infers it from missing fields. |
lifecycleID sent back with the virtual card number | Not sent | The platform keeps the card scheme's reference to the first payment against the token and attaches it to every later charge. |
| Virtual card numbers you already hold | Converted on first use: payment method LEGACY_TOKEN after Teya imports your stored cards | Customers never re-enter a card. Do this after your one-off payments are live. See Stored cards & recurring. |
Card numbers you store on your own systems (never passed through getVirtualCard) | No import | Talk to your Teya partner contact before planning the move. Only values issued by the legacy platform can be converted. |
| B-Gateway | New platform | Notes |
|---|---|---|
Batches: Batch and Transaction numbers in every response; automatic close at midnight GMT; newBatch to close earlier | No batches | Settlement is automatic. Delete batch handling; key your records on transaction_id. |
| Batch authorisations and processing results (many transactions in one request) | No batches | Charge each saved card with its own POST /v3/transactions/online request. Contact Teya support if your volumes make this a concern. |
getTransactionList | One payment: GET /v2/checkout/sessions/{session_id}. All payments: the Business Portal | Store the transaction_id from the webhook against each order. |
| Settlement list, batches and transactions in settlement, chargebacks; disputed and fraud transaction lists | The Business Portal | Settlement itself is unchanged: same merchant account, same payout timing and statements. |
Other features: sendDetailData (invoice detail), passenger itinerary data, Dynamic Descriptors (MerchantName, MerchantCity, …) | Depends on your case | Contact Teya support with your specific case before migrating, so we can check it with you. Order lines can be sent with the session as line_items. |
| Receipts (none on B-Gateway) | POST /v1/transactions/{id}/receipts | New: emails a Teya-generated receipt. See Payment Actions. |
| B-Online for refunds and cancellations | The Business Portal and the Teya app, for payments taken on the new platform | Payments taken on B-Gateway before you switch are refunded in B-Online, as today. |
| B-Gateway | New platform | Notes |
|---|---|---|
TrAmount: two implied decimals for every currency, including ISK (100 ISK = 10000); TrCurrencyExponent on 3D Secure requests | amount.value: an integer in minor units per ISO 4217. ISK has no subunit (100 ISK = 100; €54.43 = 5443) | If you charge in ISK, divide B-Gateway amounts by 100: the most likely silent bug in an ISK migration. Other currencies are unchanged (€49.90 is 4990 on both). |
TrCurrency: ISO numeric (352, 978) | amount.currency: ISO alphabetic ("ISK", "EUR") | Lookup change. |
RRN: exactly 12 characters, your reference | merchant_reference (free-form) + optional metadata | Write the legacy RRN or order number into metadata during the transition if your back office keys on it. |
DateAndTime | Not sent | Teya timestamps payments server-side. |
PAN, ExpDate, CVC2 | Not sent | Entered by the customer on the Teya page or in the Teya-hosted fields. |
Version (1000), XML request and response | JSON over HTTPS (REST) | There is no server-side SDK; plain HTTPS on the server, the JavaScript SDK in the browser for Embedded Components. |
For partners and platforms that redirect the customers of the merchants they serve to a SecurePay payment page. One server-side call replaces the signed form post; customers keep paying on a Teya-hosted page.
securepay.borgun.is (or test.borgun.is/SecurePay) to pay, and they come back to the merchant's site afterwards.checkhash and confirms payment from an orderhash.displaymode=iframe) — Hosted Checkout does not support iframes.SecurePay runs on Teya's legacy platform, which is retiring in early 2027; onboarding to it stops soon. The new platform is an upgrade, not a like-for-like swap.
Hosted Checkout Recommended — the like-for-like path for a SecurePay redirect: lowest effort, same customer experience.
displaymode=iframe. The guide opens with a side-by-side comparison to help you choose.A small development project — days including testing, not weeks. A plain-HTML site with no backend sits at the upper end: the new platform needs one small server-side component (Step 1 below).
```admonition icon: fas fa-paper-plane title: What to send your developers content: (1) Ask your Teya partner contact for a staging partner account and development store. (2) Forward this page. (3) Ask them to confirm whether any merchants usedisplaymode=iframe, the Ticket API or recurring payments — those have their own guides.
```
What replaces each SecurePay form parameter, and the one request you need at each step.
Today (SecurePay)
checkhash-signed form straight to SecurePay.returnurlsuccess and posts an orderhash to returnurlsuccessserver.After (Hosted Checkout)
session_id and session_url.session_url; they pay there, 3D Secure included.success_url (or cancel_url) and sends a signed webhook to your server.The biggest change: the browser no longer talks to Teya directly — your server makes one API call first, and confirmation comes from the server side, never the redirect.
Credentials are issued per store through the merchant registration flow: the merchant approves once from their Teya account, and Teya posts store_id, client_id, client_secret and payment_webhook_public_key to your registration webhook. Develop against a staging partner account and development store from your Teya partner contact; staging and production credentials never mix.
SecurePay equivalent: merchantid + paymentgatewayid become the Store ID; the secret key becomes the OAuth client credentials.
Staging is api.teya.xyz, id.teya.xyz and business.teya.xyz; production is the same hosts on .com. Full table: Hosted Checkout Get Started.
Teya sends a signed webhook when a payment succeeds. Pass a public HTTPS endpoint with no authentication as paymentsWebhook in the registration URL; its public key arrives as payment_webhook_public_key in the registration callback, per webhook and per environment.
SecurePay equivalent: returnurlsuccessserver and its orderhash HMAC — same purpose, verified with an RSA signature instead. The webhook fires only for successful payments (payment.succeeded.v1); failures are covered by the session lookup in Step 5. See Webhooks.
Search your codebase for borgun.is, securepay, checkhash, orderhash, merchantid, paymentgatewayid, returnurlsuccess, returnurlsuccessserver, returnurlcancel, returnurlerror, itemdescription_, itemcount_, itemunitamount_, itemamount_, skipreceiptpage, merchantlogo, pagetype, displaymode. Each hit is a line on your to-do list. Found displaymode=iframe? Use SecurePay → Embedded Components instead.
| SecurePay | Hosted Checkout | What it means for you |
|---|---|---|
merchantid + paymentgatewayid | store_id | One UUID replaces both. |
Secret key + HMAC checkhash per request | client_id + client_secret → OAuth Bearer token | No per-request signing. Cache the token and refresh from expires_in (currently 15 minutes in production, 24 hours in staging). |
| HTML form posted from the browser | POST /v2/checkout/sessions from your server, then redirect to session_url | The biggest change. Needs a backend (Step 1). |
amount as a decimal string (54,43) | amount.value, integer in minor units (5443) | ×100 for EUR, GBP, USD, CZK, HUF. ISK has no subunit: 100 ISK is 100. |
currency | amount.currency | Same ISO 4217 codes. HRK is gone; Croatia uses EUR. |
orderid (12 alphanumeric) | merchant_reference + optional metadata | Free-form. Keep the legacy orderid in metadata during transition. |
reference | metadata keys | Up to 10 key-value pairs. |
language (IS, EN, CZ…) | language, BCP 47 (is-IS, cs-CZ) | Optional; browser language if omitted. |
buyername, buyeremail | customer.name, .email, .phone_number | Prefill only; they don't force collection. |
pagetype=1 | required_customer_fields: ["SHIPPING_ADDRESS", "PHONE", "EMAIL"] | Explicit per field; returned in the session lookup. |
itemdescription_0, itemcount_0, itemunitamount_0 | line_items[].description, .quantity, .unit_price | JSON array; unit price in minor units. Shown on the payment page. |
returnurlsuccess | success_url | Still a browser redirect; never a payment confirmation. |
returnurlcancel | cancel_url | Rendered as a Back option on the checkout. |
returnurlerror | failure_url | Terminal errors only. Declined cards stay on the checkout to retry. |
returnurlsuccessserver + orderhash POST | Webhook payment.succeeded.v1, RSA-signed (x-teya-signature) | Verify the signature, then fulfil. Success-only. |
Reading orderhash on the success return | Signed webhook, or GET /v2/checkout/sessions/{session_id} | Never trust the redirect to confirm success. |
skipreceiptpage=1 | post_success_payment: "REDIRECT" | Straight to your success page. SHOW_SUCCESS_PAGE shows Teya's receipt first. |
merchantlogo | Business Portal setting | Logo, checkout and receipt messages, privacy and T&C URLs are configured once — see Customize. |
displaymode=iframe | Not supported | Use Embedded Components. |
| Verified by Visa / SecureCode setup | Automatic 3D Secure | No code, no configuration. |
| Dynamic Currency Conversion | Automatic | Offered when the card currency differs from the transaction currency. |
| (no equivalent) | Pre-authorisation + capture | New. Session type: "PRE_AUTHORISATION"; see After the payment. |
| Ticket API | Payment Links (POST /v2/payment-links) | See Ticket API → Payment Links. |
| Recurring / stored cards | Card on File | One-off payments first, then Stored cards & recurring. |
| Refunds by manual process | POST /v3/refunds, or the Business Portal | Full, partial and multiple refunds; no code for Portal refunds. |
Requests are cURL against staging. Other languages: see Hosted Checkout Get Started.
SecurePay worked from a plain HTML page because the browser posted a signed form. Hosted Checkout uses OAuth credentials that must never reach the browser, so token fetching, session creation and webhook handling run server-side. If you already have a backend, skip this step. If your site is static, a single endpoint is enough — a PHP file on your host, or a serverless function.
Exchange client_id and client_secret for a Bearer token. This replaces the per-request checkhash: cache the token, derive the TTL from expires_in, refresh before expiry or on any 401.
Things to get right: one cache per environment; don't fetch a token per payment; add captures/create if you use pre-authorisation. Full reference: Hosted Checkout Get Started.
When the customer clicks Pay, your server creates a session. This replaces the SecurePay form: every parameter you put in the form goes in this JSON body.
```bash curl -X POST https://api.teya.xyz/v2/checkout/sessions \ -H "Content-Type: application/json" \ -H "Authorization: Bearer $ACCESS_TOKEN" \ -H "Idempotency-Key: $(uuidgen)" \ -d '{ "store_id": "123e4567-e89b-12d3-a456-426614174000", "type": "SALE", "amount": { "value": 5443, "currency": "EUR" }, "merchant_reference": "ORDER-2026-00042", "line_items": [ { "description": "Widget Pro", "quantity": 2, "unit_price": 2500 }, { "description": "Delivery", "quantity": 1, "unit_price": 443 } ], "customer": { "name": "Jan Novák", "email": "jan@example.com" }, "success_url": "https://yourstore.com/order/success", "cancel_url": "https://yourstore.com/cart", "metadata": { "legacy_orderid": "ORDER1230001" } }' ``` ```json { "session_id": "14411456-79c8-4d8c-8498-f01907093b83", "session_url": "https://checkout.teya.com/session/14411456-79c8-4d8c-8498-f01907093b83", "session_status": "ACTIVE" } ```Things to get right:
amount.value is an integer in minor units: €54.43 is 5443; 100 ISK is 100. The most common bug in SecurePay migrations.Idempotency-Key is required — unique per order attempt; reuse it only to retry the identical request.session_id against the order before redirecting.Full reference: Hosted Checkout Get Started, API Reference.
Send the browser to the session_url exactly as returned. As with SecurePay, the customer enters their card on Teya's page, not yours.
The customer lands on success_url, cancel_url, or failure_url on a terminal error. Unlike SecurePay, the redirect carries no payment data. Fulfil from the webhook (Step 6) and use the lookup to cover the gaps: a customer on your success page before the webhook arrives, or a failed payment, which produces no webhook.
Things to get right: session_status runs ACTIVE → PROCESSING → COMPLETED or EXPIRED; trust payment_status (NONE, SUCCESS, FAILED) only once COMPLETED. Store the transaction_id for refunds and receipts. Completion logic that runs only on the success page drops orders from customers who close the browser after paying. Full reference: API Reference.
Teya POSTs a payment.succeeded.v1 event to your webhook URL when a payment succeeds — the equivalent of returnurlsuccessserver, verified by RSA signature instead of an orderhash HMAC, and delivered whether or not the customer returns.
x-teya-signature header (SHA256withRSA, Base64) against the webhook's public key over the entire raw body, before parsing.data.session_id or data.merchant_reference; store data.transaction_id.200 promptly; do slow work asynchronously.Full reference, including the payload: Webhooks.
Refunds, captures and receipts return their outcome in the synchronous API response; there is no webhook for them. All are visible in the Business Portal.
On SecurePay, refunds were a manual process. Now: POST /v3/refunds with the transaction_id and an amount in minor units (scope refunds/create). Partial and multiple refunds are allowed up to the original amount; 201 is processed, 202 is still processing — retry with the same Idempotency-Key. Staff can also refund in the Business Portal with no code. See Payment actions.
Use the session lookup from Step 5 for order-level status and the Business Portal for reporting. Keep the transaction_id on every order.
New: POST /v1/transactions/{transaction_id}/receipts (scope transactions/id/receipts/create) emails a Teya-generated receipt. See Payment actions.
Create the session with "type": "PRE_AUTHORISATION"; the flow is otherwise identical, and DCC is not offered on holds. Capture with POST /v1/transactions/{transaction_id}/capture (scope captures/create) for any amount up to the authorised total, once per authorisation; the remainder is released. To release a hold early, refund the uncaptured payment — it reverses the authorisation. Pre-authorisations are currently available for certain tourism-related business activities; if your business needs them for another use case, contact your Teya account manager. Capture is API-only today. See Payment actions.
Do all of this in staging with the cards on the Testing page. If your development environment isn't publicly reachable, tunnel it so staging webhooks can reach you.
COMPLETED/SUCCESS; order fulfilled oncesuccess_urlcancel_url; no webhook; not fulfilled401 by refreshingid.teya.xyz → id.teya.com, api.teya.xyz → api.teya.com.What a SecurePay developer trips on. For the general catalogue, see Troubleshooting.
| Symptom | Likely cause | Fix |
|---|---|---|
| Charged amount is 100× wrong | Minor-units conversion missed, or applied to ISK | €54.43 = 5443; ISK face value as-is |
401 once the access token expires (currently about 15 minutes in production) | Token cached like a static secret key | TTL from expires_in; refresh on 401 |
| Customer sees "Session expired" | Session created on cart load | Create it at the Pay click |
| Orders marked paid that weren't, or never marked paid | Fulfilling from the redirect, as with orderhash | Fulfil from the webhook; back it with the session lookup |
| Webhook never arrives | Endpoint not public, has auth, bad certificate, or returns non-200 | Public HTTPS, no auth; allowlist Teya's webhook IPs if you filter inbound traffic; always return 200 |
| Signature verification fails | Wrong key, or body parsed before verifying | Key for this webhook in this environment; verify the raw body; use the Signature Validator |
| Payment succeeded but not in the Portal | Wrong environment | Staging in business.teya.xyz, production in business.teya.com |
| "No compatible stores found" when creating credentials | Store lacks the ecommerce subscription | Ask your Teya contact to add it |
For partners and platforms whose SecurePay integration serves the merchants you serve, and who want the payment form inside your own pages, no redirect. Part 1 is a summary you can forward to a merchant or product owner; Part 2 is for your developers.
displaymode=iframe. Hosted Checkout does not support iframe embedding; Embedded Components is the path that keeps the form on the page.SecurePay runs on Teya's legacy platform, which is retiring in early 2027; onboarding to it stops soon. The new platform is an upgrade rather than a swap — and Embedded Components is a chance to improve the checkout, not just replace it.
Both use the same credentials, session API and webhooks, and both keep you SAQ A. The difference is where the customer pays and how much front-end work that takes.
| Hosted Checkout | Embedded Components | |
|---|---|---|
| Customer experience | Redirect to a Teya-hosted page and back | Pays on your page, no redirect |
| Development effort | Lower — one server-side call and a redirect | Moderate — the same call plus a front-end SDK integration |
| Branding and layout | Logo and messages on Teya's page | Your page, your layout; themeable fields |
| Apple Pay | — | Built into the Checkout component |
| 3DS, DCC, refunds, webhooks, PCI | Identical | |
Choose Hosted Checkout if you want the least effort and checkout design isn't a differentiator — see SecurePay → Hosted Checkout. Choose Embedded Components if your integration was embedded, conversion justifies removing the redirect, or you want Apple Pay. The server side is identical, so you can start with Hosted Checkout and move later without redoing it.
Checkout component Recommended — a complete payment form (card fields, pay button, Apple Pay, DCC) in one block. Least code; right for most migrations.
A small development project — days including testing, not weeks. It sits a notch above the Hosted Checkout path because of the front-end work. SecurePay keeps working in parallel until you switch it off.
```admonition icon: fas fa-paper-plane title: What to send your developer content: (1) A link to this page, pointing at Part 2. (2) Access to a development store — ask your Teya contact. (3) Two questions to answer back: does the integration need pre-authorisations, and does it use stored cards or recurring payments? Recurring is migrated last — see stored cards & recurring. ```Replace the signed SecurePay form (redirect or iframe) with a checkout session created on your server and the Checkout component mounted on your page.
Today (SecurePay):
checkhash to SecurePay — a redirect or an iframe (displaymode=iframe).returnurlsuccess and posts an orderhash to returnurlsuccessserver.orderhash and fulfils the order.After (Embedded Components):
session_token.onSuccess / onError callback fires, and Teya sends a signed webhook to your server — the source of truth for fulfilment.The biggest change: the signed form post, checkhash, orderhash check and any iframe plumbing are deleted — the component replaces them, and there are no return URLs at all.
Each merchant's store_id, client_id, client_secret and webhook public key arrive through the merchant registration flow — the merchant approves once from their Teya account. For a larger base, agree a batch approach with your Teya partner contact. Build and test first against a staging partner account and development store.
SecurePay equivalent: merchantid + paymentgatewayid become the Store ID; the checkhash secret key becomes the Client ID and Client Secret.
| Staging | Production | |
|---|---|---|
| API | https://api.teya.xyz | https://api.teya.com |
| OAuth | https://id.teya.xyz/oauth/v2/oauth-token | https://id.teya.com/oauth/v2/oauth-token |
| SDK | developmentMode: true | flag omitted |
Register a public HTTPS endpoint with no authentication for each merchant; the merchant's payment_webhook_public_key is returned in the registration callback and used in Step 5.
SecurePay equivalent: returnurlsuccessserver + orderhash, verified with an RSA signature instead of a shared-secret hash. Only successful payments produce a webhook; failures surface in onError and via session lookup.
Search your codebase for: borgun.is, securepay, checkhash, orderhash, merchantid, paymentgatewayid, returnurlsuccess, returnurlsuccessserver, returnurlcancel, returnurlerror, itemdescription_, itemcount_, itemunitamount_, itemamount_, displaymode, skipreceiptpage, merchantlogo, pagetype. Each hit is a line on your to-do list.
session_tokenonSuccess is for UX; the webhook is the source of truth
| SecurePay (today) | Embedded Components (new) | What it means for you |
|---|---|---|
merchantid + paymentgatewayid | store_id | One UUID replaces both. |
Secret key + HMAC checkhash | client_id + client_secret → OAuth Bearer token | No per-request HMAC. Cache and refresh from expires_in (currently 15 min in production, 24 h in staging). |
| HTML form posted to SecurePay | POST /v2/checkout/sessions → SDK mounts the component with the session_token | The browser never calls Teya's API; it only hosts the component. |
displaymode=iframe + iframe result plumbing | The component is the embedded experience | Teya-hosted iframe fields; your iframe glue code goes. |
amount as a decimal string (54,43) | amount.value — integer in minor units (5443) | ×100 for EUR, GBP, CZK, HUF; ISK has no subunit — 100 ISK is 100. |
currency | amount.currency — ISO 4217 alphabetic | ISK, EUR, CZK, HUF, GBP… |
orderid (max 12 alphanumeric) | merchant_reference + optional metadata | Write the legacy orderid into metadata during transition if it helps reconciliation. |
language | SDK locale option | Set on initTeyaBlocks. |
buyername, buyeremail | customer.name, customer.email on the session | Prefill only. |
itemdescription_0 … | line_items[] on the session | Description, quantity, unit price in minor units. |
returnurlsuccess / returnurlcancel / returnurlerror | onSuccess / onError callbacks | No redirects; your page decides what happens next. |
returnurlsuccessserver + orderhash | Webhook payment.succeeded.v1, RSA-signed | Verify the signature over the raw body, then fulfil. |
skipreceiptpage, merchantlogo, pagetype | Not applicable — there is no Teya page | Theme the component to match your page. |
| Verified by Visa / MC SecureCode | Automatic 3D Secure inside the component | Challenge renders inline. Nothing to build. |
| Dynamic Currency Conversion | Automatic in the Checkout and Card components | Nothing to implement. |
| (no equivalent) | Apple Pay | In the Checkout component; one-off domain verification (Step 3). |
| (no equivalent) | Pre-authorisation + partial capture | See After the payment. |
| Ticket API | Payment links | Ticket API → Payment Links |
| Recurring payments / stored cards | Card on File | Migrate last — stored cards & recurring. |
| Refunds via portal / manual | POST /v3/refunds or the Business Portal | Partial and multiple refunds. |
Steps 1–5 build the Checkout component end to end, in cURL against staging. Other languages, and the interactive quickstart, are on the Embedded Checkout get started page.
Exchange client_id and client_secret for a short-lived Bearer token; this replaces checkhash signing. Add captures/create to the scopes if you use pre-authorisations.
Things to get right: derive the cache lifetime from expires_in (currently 15 min production, 24 h staging); refresh when under a minute remains or on any 401; one cache per environment. Full reference: Authenticate.
When the customer reaches payment, create a session describing the intent — amount, currency, reference. The response carries the session_token your page hands to the SDK. This replaces the signed form; no card data is involved.
Things to get right:
amount.value is an integer in minor units — €54.43 is 5443; ISK face value as-is. The most common migration bug.Idempotency-Key is required and unique per order attempt.session_token is single-use and the session expires — create it when the customer reaches payment, not on cart load; create a fresh one for a retry.session_token to the front end through a small endpoint of your own (e.g. POST /api/teya-session) — never the access token or client secret. Store the session_id against the order first.Full reference: Embedded Checkout · API reference.
Install the SDK (npm install @teyaproduct/teya-blocks-js; React also @teyaproduct/teya-blocks-react), initialise it with the session token from your backend, and mount the component into a container. This replaces the SecurePay form or iframe.
Things to get right:
developmentMode: true is required in staging — without it the SDK targets production and the staging token is rejected. Gate it on environment config; never ship it to production.appearance — presets, CSS variables and fonts on the Customize page.Full reference: SDK Initialization · Embedded Checkout (HTML/JS and React).
For your own form layout and pay button, mount a card element instead and call card.submitPayment() from your button; use its onChange event (event.complete) to enable the button. Session and webhook are unchanged; Apple Pay is a separate element. See Card element.
Use onSuccess / onError for the user experience only; fulfil from the webhook. If the customer closed the page mid-payment or the webhook is delayed, look the session up by ID — this replaces the returnurlsuccess redirect and orderhash check.
Things to get right: session_status runs ACTIVE → PROCESSING → COMPLETED / EXPIRED; trust payment_status only once the session is COMPLETED. Store the transaction_id from the completed session or the webhook — you need it for refunds and receipts. Full reference: API reference.
Teya posts a signed payment.succeeded.v1 event to your webhook URL when a payment succeeds — your fulfilment trigger, replacing returnurlsuccessserver, and one that no longer depends on the customer's browser. Verify the x-teya-signature header (SHA256 with RSA, Base64) against the public key for this webhook, over the entire raw body, before parsing. Match the order on data.merchant_reference or data.session_id.
Things to get right:
200 promptly; do slow work asynchronously.Full reference: Webhooks — payload fields, verification steps and sample code.
Identical to the Hosted Checkout path — the server side doesn't care how the payment was collected. Refunds and captures report their outcome in the synchronous response; there is no webhook for them.
Refunds. Full, partial and multiple refunds up to the original amount — by API or in the Business Portal with no code. 201 is processed; 202 is still processing, retry with the same idempotency key. A DCC payment is refunded in the currency the customer chose.POST /v3/refunds · scope refunds/create · Payment actions
Status lookup. Reconcile by session; store the transaction_id with each order.GET /v2/checkout/sessions/{session_id} · scope checkout/sessions/id/get · API reference
Digital receipts (new, optional). Email the customer a receipt for a completed payment.POST /v1/transactions/{transaction_id}/receipts · scope transactions/id/receipts/create · Payment actions
Pre-authorisations (new). SecurePay never offered holds; the new platform does. Pre-authorisations are currently available for certain tourism-related business activities; if your business needs them for another use case, contact your Teya account manager. Create the session with "type": "PRE_AUTHORISATION"; the component flow is otherwise identical (no DCC on pre-authorisations). Capture once per authorisation, any amount up to the total, with POST /v1/transactions/{transaction_id}/capture (scope captures/create); the remainder is released. To release a hold early, refund the uncaptured payment — that reverses it. See Payment actions.
Test in staging with developmentMode: true and the test cards on the Testing page. If your dev environment isn't publicly reachable, tunnel it so staging webhooks can reach you.
onSuccess fires, webhook verified, session COMPLETED / SUCCESS, order fulfilled once401 by refreshingid.teya.xyz → id.teya.com, api.teya.xyz → api.teya.com) and remove developmentMode: true.| Symptom | Likely cause | Fix |
|---|---|---|
| Staging session token rejected by the SDK | developmentMode: true missing | Add it in staging; remove in production |
| Payments land in the wrong environment | developmentMode shipped to production, or hosts mixed | Gate on environment config; audit hosts |
| Charged amount is 100× wrong | Decimal → minor-units conversion missed | €54.43 is 5443; ISK face value as-is |
| Component won't mount / token error | Session token already used, or session expired | One token per mount; create a fresh session |
401 on API calls | Access token expired | Refresh; derive cache lifetime from expires_in |
403 / 409 | Missing scope / same Idempotency-Key, different body | Add the scope (Step 1) / unique key per order attempt |
| Webhook never arrives | URL unreachable, firewall, or non-200 response | Public HTTPS, no auth; allowlist Teya IPs; return 200 |
| Signature verification fails | Wrong key, or a parsed body | Per-webhook, per-environment key; verify the raw body; use the Signature Validator |
| Apple Pay button doesn't render | Domain not verified in this environment | Verify staging and production separately |
initTeyaBlocks() options and developmentModeFor partners and platforms whose systems talk directly to RPG (Borgun Payment API) on behalf of the merchants you serve. The first half is a summary you can forward; the second half is the developer guide.
borgun.is, rpgapi or /api/payment, authenticated with a Private Access Token./api/mpi/v2/enrollment and /api/mpi/v2/validation.BAPIjs / borgun-payment.js to create single-use tokens.securepay.borgun.is, payment URLs created in advance, and stored cards charged without the customer present each have their own guide.
link: /partners/online-payments/migration/overview
```
RPG runs on Teya's legacy platform, which is retiring in early 2027; onboarding to it stops soon. The new platform is an upgrade, not a like-for-like swap: it removes the parts of RPG that cost the most to build and maintain.
Checkout component Recommended — the closest match to an RPG checkout. The customer stays on your page; one block provides card fields, pay button, 3D Secure, Apple Pay and currency conversion.
For a one-off-payments RPG integration this is a small development project; pre-authorisations and custom logic sit at the upper end. RPG keeps working in parallel until you switch it off.
```admonition icon: fas fa-paper-plane title: What to line up before you build content: (1) A staging partner account and development store from your Teya partner contact. (2) The list of merchants on this integration, so credentials can be exchanged store by store. (3) Whether any of them use pre-authorisations or stored cards / recurring payments (multi-use tokens) — both change the plan. ```How an RPG integration maps onto a checkout session and the Checkout component, with the one request you need at each step.
Today (RPG)
BAPIjs collects the card; card data or a single-use token reaches your server.TermURL callback, validation, MdStatus.POST /api/payment with the ThreeDSecure block and reads ActionCode.PaymentCreate webhook arrives and you call back GET /api/events/{EventId}.After (Embedded Components)
onSuccess fires for the user experience; a signed webhook confirms the payment to your server and drives fulfilment.The biggest change: the whole MPI flow and all card-data or single-use-token handling are deleted — the component replaces both.
Each store gets its own client_id, client_secret, store_id and webhook public key. For merchants already on RPG, exchange each merchant's RPG private key for new credentials — one call per store, production only. New merchants come through the registration flow. For staging, ask your Teya partner contact for a development store and create credentials in the Staging Business Portal.
Staging is api.teya.xyz / id.teya.xyz / business.teya.xyz with developmentMode: true in the SDK; production is the same hosts on .com without the flag. Full table on Embedded Checkout.
Teya sends a signed payment.succeeded.v1 event to a public HTTPS endpoint with no authentication on it.
Pass the URL as webhook_url in the credential exchange and store the returned payment_webhook_public_key against that store.
Unlike RPG's PaymentCreate / Refund / Capture events, only successful customer payments produce a webhook today; refunds and captures you trigger return their outcome in the API response. See Webhooks.
Search for borgun.is, rpgapi, /api/payment, /api/token/single, /api/mpi, TransactionType, ActionCode, MdStatus, TermURL, MpiToken, BAPIjs, borgun-payment, PrivateAccessToken, BorgunRPGNet, RPGClient. Each hit is a line on the to-do list. The .NET SDK is a thin wrapper over the same endpoints, so the mapping below applies to its methods one-to-one; the new platform has no server-side SDK — plain HTTPS on the server, the JS SDK in the browser.
The RPG capability reference maps each capability to its replacement. This table goes one level down: the calls and fields an RPG developer has to change.
| RPG today | New platform | What it means for you |
|---|---|---|
| Private Access Token (permanent, Basic auth) | client_id + client_secret → short-lived Bearer token | Cache and refresh from expires_in (currently 15 min in production, 24 h in staging). |
| Public Access Token (browser tokenisation) | Not needed — the session token comes from your backend per payment | Delete with the BAPIjs code. |
POST /api/payment, TransactionType: "Sale" | POST /v2/checkout/sessions, type: "SALE" + the component | The server sends intent only — amount, currency, reference. No card data, no 3DS block. |
POST /api/payment, TransactionType: "PreAuthorization" | type: "PRE_AUTHORISATION" on the same endpoint | See Pre-authorisations below. |
PUT /api/payment/{id}/capture (full only) | POST /v1/transactions/{id}/capture, {"amount": n} | Partial capture gained. Once per authorisation; the remainder is released. |
PUT /api/payment/{id}/cancel | POST /v3/refunds on the uncaptured payment | A refund before capture reverses the authorisation. No separate cancel. |
PUT /api/payment/{id}/refund + PartialAmount | POST /v3/refunds with transaction_id, amount — or the Business Portal | Same semantics: partial, multiple, total ≤ original. |
GET /api/payment/{id} | GET /v2/checkout/sessions/{session_id} | session_status + payment_status instead of TransactionStatus. |
POST /api/token/single + BAPIjs.getToken | Gone — the component's hosted fields do this | No token creation, lifetime management or disable calls. |
/api/mpi/v2/enrollment, /validation, TermURL, MdStatus, MpiToken | Gone — 3D Secure runs inside the component, challenge inline | Delete the whole flow. |
POST /api/payment with raw PAN | Direct API — restricted, PCI SAQ D evidence required | Use the component unless you have no alternative. |
POST /api/token/multi, VerifyCard | Card on File | Separate track — Stored cards & recurring. |
Amount — 2 implied decimals, including ISK (100 ISK = 10000) | amount.value — minor units per ISO 4217; ISK has no subunit (100 ISK = 100, €54.43 = 5443) | Divide RPG ISK amounts by 100. The most likely silent bug in an ISK migration; other currencies are unchanged. |
Currency — ISO numeric ("352") | amount.currency — ISO alphabetic ("ISK") | Lookup change. |
OrderId — exactly 12 chars, A–Z/0–9 | merchant_reference (free-form) + optional metadata | Write the legacy OrderId into metadata during transition if it helps reconciliation. |
TransactionDate | Not sent — Teya timestamps server-side | Delete. |
ActionCode ("000" = approved) + TransactionStatus | payment_status (SUCCESS / FAILED) on the session | No action-code tables; declines show in the component and the customer retries in place. |
DynamicDescriptors | Not exposed on checkout sessions | Tell your Teya contact before migrating if you rely on them. |
PaymentCreate webhook + GET /api/events/{EventId} | payment.succeeded.v1, RSA-signed (x-teya-signature) | Verify by signature instead of calling back. |
| B-Online | Business Portal | Credentials, webhooks, refunds, transaction visibility. |
Steps 1–5 build the Checkout component end to end. Samples are cURL and minimal JavaScript; other languages are on the Embedded Checkout Get Started page.
Exchange client_id + client_secret for a Bearer token. This replaces the permanent Private Access Token: cache the token per environment and refresh it before expires_in runs out or on a 401. Scopes for this guide: checkout/sessions/create, checkout/sessions/id/get, refunds/create, plus captures/create for pre-authorisations.
Full reference: Authenticate.
When the customer clicks Pay, your server describes the payment and gets back a session token for the page. This replaces POST /api/payment, but the body carries no card data and no 3DS block — only the intent.
Things to get right
amount.value is an integer in minor units per ISO 4217: 100 ISK = 100, not RPG's 10000.Idempotency-Key is unique per order attempt; retries with the same key can't create duplicate sessions.session_token to the browser through a small endpoint of your own — never the access token or the Client Secret.Full reference: API Reference.
Install @teyaproduct/teya-blocks-js, initialise the SDK with the session token from your backend, and mount the component. This replaces your card form and BAPIjs.getToken: the fields are Teya-hosted, so card data never enters your DOM.
Things to get right
developmentMode: true; a production token only works without it. Gate the flag on your own environment.MdStatus are no longer your code.Full reference: SDK Initialization and Embedded Checkout.
For your own form layout and pay button, create a card element instead and call card.submitPayment() from your button; the session and everything after are the same. No built-in Apple Pay button. See Card Element.
onSuccess / onError are for the user experience: navigate to a confirmation page, show an error state. The webhook (Step 5) is the source of truth for fulfilment. As a fallback (customer closed the browser, webhook delayed), look the session up by ID. This replaces GET /api/payment/{id} and the "only 000 means approved" rule.
Things to get right
session_status runs ACTIVE → PROCESSING → COMPLETED or EXPIRED; trust payment_status only once COMPLETED.transaction_id from the webhook against the order — it is the key for refunds and receipts.Full reference: API Reference.
Teya POSTs a signed payment.succeeded.v1 event to your registered URL when a payment succeeds — the equivalent of RPG's PaymentCreate, verified by signature instead of a call back to GET /api/events/{EventId}. Verify x-teya-signature (SHA256withRSA, Base64) over the raw request body with the public key issued for this webhook, before parsing anything.
Things to get right
200 promptly and do slow work asynchronously.Full reference: Webhooks.
Each operation returns its outcome in the synchronous API response — there is no webhook for refunds or captures today, so record the response.
Replaces PUT /api/payment/{id}/refund, same rules: partial and multiple refunds, total ≤ original. 201 is processed; 202 is still processing — retry with the same Idempotency-Key. Staff can also refund from the Business Portal. Payments taken on RPG before cutover stay refundable through B-Online for a transition period.
See Payment Actions.
Status: GET /v2/checkout/sessions/{session_id} (Step 4) replaces GET /api/payment/{id}; the Business Portal covers day-to-day visibility. Receipts are new — POST /v1/transactions/{id}/receipts emails a Teya-generated receipt (scope transactions/id/receipts/create). See Payment Actions.
Skip this if you only send TransactionType: "Sale". Create the session with type: "PRE_AUTHORISATION"; the component flow is unchanged and the funds are held (no DCC on pre-auth). Capture with POST /v1/transactions/{id}/capture and {"amount": n} — any amount up to the authorised total, but exactly once: the remainder is released, so capture the final amount. There is no cancel call: refund the uncaptured payment to reverse the authorisation; untouched holds expire on their own. Pre-authorisations are currently available for certain tourism-related business activities; if your business needs them for another use case, contact your Teya account manager. Capture is API-only today; confirm before migrating pre-auth flows. See Payment Actions.
Test in staging with developmentMode: true and the cards on the Testing page — frictionless, challenge and declined scenarios are all there. If your development machine isn't publicly reachable, tunnel it so staging webhooks can reach you.
Definition of done
onSuccess fires, webhook received and verified, session COMPLETED / SUCCESS401 by refreshing the tokenGo live
webhook_url) and store the credentials and public key per store..xyz → .com) and remove developmentMode.| Symptom | Likely cause | Fix |
|---|---|---|
| Charged amount is 100× too big (ISK) | RPG's two-implied-decimals convention carried over | Divide legacy ISK amounts by 100 |
| Staging session token rejected by the SDK | developmentMode: true missing | Add it in staging, remove in production |
401 on API calls | Access token expired — currently 15 min in production, not permanent like the RPG token | Refresh from expires_in; retry once on 401 |
403 | Missing scope | Add the scope to the token request (Step 1) |
409 | Same Idempotency-Key, different body | Unique key per order attempt; reuse only for retries of the identical request |
422 creating a session | Numeric currency code or non-integer amount | Alphabetic currency; integer amount.value in minor units |
| Webhook never arrives | URL not public, no valid certificate, auth on the endpoint, or non-200 responses | Public HTTPS, no auth, return 200 after processing |
| Signature verification fails | Parsed body re-serialised, or the wrong key | Verify the raw body; use the key issued for this webhook in this environment |
| Payment succeeded but not in the Business Portal | Wrong environment | Staging payments are in business.teya.xyz, production in business.teya.com |
| Waiting for a refund or capture webhook | Only successful customer payments send one | Read the outcome from the API response |
For partners and platforms whose systems send payments to B-Gateway (the Borgun payment gateway web service, SOAP/XML; also written BGateway or Greiðslugátt) on behalf of the merchants you serve. The first half is a summary you can forward; the second half is the developer guide.
getAuthorization to gateway01.borgun.is, with a B-Gateway username and password.9910…) and send them on to B-Gateway.getAuthorization call for payments where a customer is checking out and for charges you run later without them (subscriptions, instalments, top-ups). You may also run 3D Secure through get3DSAuthentication or your own or a third-party MPI./api/payment (the RPG payment API), redirects to securepay.borgun.is, and payment URLs created in advance each have their own guide.
link: /partners/online-payments/migration/overview
```
B-Gateway runs on Teya's legacy platform, which is retiring in early 2027. The new platform is an upgrade, not a like-for-like swap: it removes the parts of a B-Gateway integration that cost the most to build and maintain.
Checkout component Recommended — the closest match to a B-Gateway checkout with the least to maintain. The customer stays on your page; one block provides card fields, pay button, 3D Secure, Apple Pay and currency conversion. This guide follows it.
This is a small development project, and the result is less code than you have today: the SOAP client, the card form, any 3D Secure flow and batch handling are all removed. B-Gateway keeps working in parallel until you switch it off. If most of your payments are subscriptions or charges on cards you hold, step 5 and the charging of saved cards are the bulk of the work; the one-off checkout still comes first, because it is how new cards get saved from now on. Stored cards move on their own track, without any customer re-entering a card.
```admonition icon: fas fa-paper-plane title: What to line up before you build content: (1) A staging partner account and development store from your Teya partner contact; card saving (tokenization) is enabled by default, but enrolment can take up to 48 hours. (2) The list of merchants on this integration, with the B-GatewayMerchantID you hold for each, so credentials can be set up store by store. (3) Which of them charge cards without the customer present (virtual card numbers from B-Gateway, or card numbers you store), use pre-authorisations, or use any other B-Gateway features (telephone or keyed orders, reports pulled from B-Gateway, Dynamic Descriptors, detail or itinerary data). Each changes the plan.
```
How a B-Gateway integration maps onto a checkout session, Hosted Checkout or the Checkout component, and token payments, with the one request you need at each step.
Today (B-Gateway)
get3DSAuthentication or your own MPI, sends the customer to the bank and gets CAVV / UCAF / XID back.getAuthorization (SOAP/XML, username and password, Processor, MerchantID, TransType, RRN) with the card or a virtual card number, plus 3D Secure values if you have them, and reads ActionCode. The same call is used whether or not the customer is present.getVirtualCard and later send getAuthorization with it, without the customer.DateAndTime + RRN; batches close at midnight GMT or on newBatch.After (new platform)
save_payment_method: true.POST /v3/transactions/online, marked as merchant-initiated.transaction_id. There are no batches.The biggest change: card numbers no longer pass through your systems, and each payment is either a checkout session (customer present) or a charge on a saved card (merchant-initiated). The SOAP client, the card form, any 3D Secure flow, getVirtualCard and batch handling are all deleted.
List every place your code calls getAuthorization and decide, for each one, whether a customer is present. On B-Gateway the same request serves both; on the new platform they are different calls:
POST /v3/transactions/online on a saved token, declared merchant-initiated (Step 6). No 3D Secure, immediate result.ACCOUNT_VERIFICATION session with save_payment_method (Step 2).Every item on your list becomes either a session or a token charge. A charge without the customer is only possible on a card they saved with you, so if you hold card numbers that were never saved through Teya, see Stored cards & recurring before you plan Step 6.
Each store gets its own client_id, client_secret, store_id and webhook public key; together they replace the B-Gateway username, password, Processor and MerchantID you hold per merchant. Merchants come onto the new platform through the registration flow, which issues their credentials to you. If you hold B-Gateway credentials for many merchants, talk to your Teya partner contact about the approach before you start. For staging, ask for a development store. Never mix staging and production credentials. See Authenticate.
Card saving (tokenization) is enabled by default on every store, but enrolment can take up to 48 hours after the store is created. Until then, sessions with save_payment_method succeed but return no token. If that persists after 48 hours, contact support.
Staging is api.teya.xyz / id.teya.xyz / business.teya.xyz; production is the same hosts on .com. With Embedded Components, staging also needs developmentMode: true in the SDK. Full table on Hosted Checkout Get Started.
Teya sends a signed payment.succeeded.v1 event to a public HTTPS endpoint with no authentication on it. Register the URL per store when the store is set up; you receive a public key per webhook for verifying signatures. Only successful customer payments produce a webhook today; refunds, captures and token charges return their outcome in the API response. See Webhooks.
Search for gateway01.borgun.is, borgun.is, getAuthorization, cancelAuthorization, get3DSAuthentication, getVirtualCard, newBatch, getTransactionList, TransType, TrAmount, TrCurrency, RRN, AuthCode, ActionCode, Processor, CAVV, UCAF, XID, SecurityLevelInd, lifecycleID, the XML namespace tempuri.org/getAuthorization.xsd, and stored 16-digit values starting 9910. Code based on the B-Gateway demo clients (BGatewayDemoNet, BGatewayDemoPhp, BGatewayDemoPython) follows the same pattern. Each hit is a line on the to-do list. If you send TerminalID 2 (ecommerce with 3D Secure) or CAVV / UCAF values, you run 3D Secure today: also check the MPI configuration behind it.
The calls and fields a B-Gateway developer has to change. The B-Gateway reference covers every operation, including reporting and the ones with no equivalent.
| B-Gateway today | New platform | What it means for you |
|---|---|---|
| Username + password on every call | client_id + client_secret → short-lived Bearer token | Cache and refresh from expires_in (currently 15 min in production, 24 h in staging). |
Processor, MerchantID, TerminalID (1 ecommerce, 2 ecommerce with 3D Secure, 3 recurring, 4 mail/telephone order) | store_id | One identifier per store, sent in the request body. What the terminal said about the payment is now said by the request itself: a session for customer-present payments, initiator + recurring_payment_type for the rest. |
getAuthorization, TransType 1 | POST /v2/checkout/sessions, type: "SALE" + the Checkout component | The server sends intent only: amount, currency, reference. No card data, no 3D Secure values. |
getAuthorization, TransType 5 | type: "PRE_AUTHORISATION" on the same endpoint | See Pre-authorisations below. |
TransType 1 with a pre-authorisation's AuthCode | POST /v1/transactions/{id}/capture, {"amount": n} | Once per authorisation; the remainder is released. |
TransType 3 by DateAndTime + RRN | POST /v3/refunds with transaction_id, or the Business Portal | Same semantics: partial, multiple, total ≤ original. |
TransType 4, cancelAuthorization | POST /v3/refunds on the uncaptured payment | A refund before capture reverses the authorisation. No partial reversal; no open-batch rule. |
get3DSAuthentication / own MPI; CAVV, UCAF, XID, SecurityLevelInd (if you send them) | Gone: 3D Secure runs inside Hosted Checkout and the component | Delete the whole flow if you have one, including MerchantReturnURL handling. If you don't run 3D Secure today, there is nothing to delete. |
PAN, ExpDate, CVC2; your card form | Gone: entered on the Teya page or in Teya-hosted fields | Delete the card form and every place card data is logged or stored. |
getVirtualCard | save_payment_method: true on the session; token_response.token_id in the webhook | Saving happens inside the payment, or a zero-amount ACCOUNT_VERIFICATION. |
getAuthorization with a virtual card number (or a card number you store), customer absent | POST /v3/transactions/online, initiator: "MERCHANT", payment_method.type: "TOKEN" | See Step 6. Virtual numbers you already hold move later; see Stored cards & recurring. Card numbers you store yourself have no import: talk to your Teya partner contact. |
lifecycleID | Not sent | The platform links later charges to the first payment for you. |
TrAmount: 2 implied decimals, including ISK (100 ISK = 10000) | amount.value: minor units per ISO 4217; ISK has no subunit (100 ISK = 100) | If you charge in ISK, divide B-Gateway amounts by 100: the most likely silent bug. Other currencies are unchanged (€49.90 is 4990 on both). |
TrCurrency: ISO numeric (352) | amount.currency: ISO alphabetic ("ISK") | Lookup change. |
RRN (12 characters), DateAndTime | merchant_reference (free-form) + optional metadata; no timestamp | Write the legacy RRN into metadata during the transition if it helps reconciliation. |
ActionCode (000 = approved) | payment_status (SUCCESS / FAILED) on the session | Declines show in the checkout and the customer retries in place. |
Batches, newBatch, Batch / Transaction numbers | None | Delete. Key your records on transaction_id. |
getTransactionList, settlement and dispute lists | GET /v2/checkout/sessions/{session_id} for one payment; the Business Portal for the rest | See the reference for each report. |
| B-Online | Business Portal | Credentials, webhooks, refunds, transaction visibility. |
Steps 1–5 take one-off payments end to end; Step 6 adds charging a saved card. Samples are cURL and minimal JavaScript; other languages are on the Embedded Checkout Get Started page.
Exchange client_id + client_secret for a Bearer token. This replaces sending a username and password on every call: cache the token per environment and refresh it before expires_in runs out or on a 401. Scopes for this guide: checkout/sessions/create, checkout/sessions/id/get, refunds/create. Add captures/create for pre-authorisations and transactions/online/create for charging saved cards.
Full reference: Authenticate.
When the customer clicks Pay, your server describes the payment. This replaces getAuthorization, but the body carries no card data and no 3D Secure values; only the intent. For a subscription sign-up, add save_payment_method: true so the card is saved during this payment.
Things to get right
amount.value is an integer in minor units per ISO 4217: 4,990 ISK is 4990, not B-Gateway's 499000; €49.90 is 4990 on both.Idempotency-Key is unique per order attempt; retries with the same key can't create duplicate sessions. This replaces looking up an unknown result with getTransactionList.type: "ACCOUNT_VERIFICATION" with amount.value: 0 and save_payment_method: true. This replaces the zero-amount pre-authorisation.session_token in the response is what your page needs for the component (Step 3). success_url and cancel_url are only used by Hosted Checkout.Full reference: Embedded Checkout, API Reference.
Install @teyaproduct/teya-blocks-js, initialise the SDK with the session_token from your backend, and mount the component. It replaces your card form and your 3D Secure redirect: the fields are Teya-hosted, so card data never enters your page or your server.
Things to get right
developmentMode: true; a production token only works without it.session_token to the browser through a small endpoint of your own; never the access token or the Client Secret.Using Hosted Checkout instead? Skip the SDK: send the browser to the session_url returned with the session, exactly as returned. The customer pays on Teya's page and lands on your success_url or cancel_url; the redirect carries no payment data, so Steps 4 and 5 are unchanged. See Hosted Checkout Get Started.
Full reference: SDK Initialization and Embedded Checkout.
The webhook (Step 5) is the source of truth for fulfilment. As a fallback (customer closed the browser, webhook delayed, payment failed), look the session up by ID. This replaces reading ActionCode and polling getTransactionList.
Things to get right: session_status runs ACTIVE → PROCESSING → COMPLETED or EXPIRED; trust payment_status only once COMPLETED. Store the transaction_id: it is the key for refunds, replacing DateAndTime + RRN. Full reference: API Reference.
Teya POSTs a signed payment.succeeded.v1 event to your registered URL when a payment succeeds, whether or not the customer comes back to your site. Verify x-teya-signature (SHA256withRSA, Base64) over the raw request body with the public key issued for this webhook, before parsing anything. When the card was saved, the event carries the token.
Things to get right
data.token_response.token_id against the customer. It replaces the virtual card number in your subscription records. token_response is null when no card was saved.200 promptly and do slow work asynchronously.Full reference: Webhooks.
For each subscription or instalment due, charge the token without the customer present. This replaces getAuthorization with a virtual card number and no 3D Secure values. Declare it as merchant-initiated: there is no 3D Secure and the result is immediate and final.
Things to get right
recurring_payment_type: RECURRING for a fixed schedule, INSTALLMENT for a fixed number of payments, CARD_ON_FILE for unscheduled charges.Idempotency-Key per customer per billing period, so a re-run of your billing job can't charge twice.status_reason such as INSUFFICIENT_FUNDS or TEMPORARY_ISSUE_RETRY); the full list is on Charge a Saved Payment Method.Full reference: Charge a Saved Payment Method.
Each operation returns its outcome in the synchronous API response; there is no webhook for refunds or captures today, so record the response.
Replaces TransType 3 and cancelAuthorization: partial and multiple refunds, total ≤ original, referenced by transaction_id instead of DateAndTime + RRN. 201 is processed; 202 is still processing: retry with the same Idempotency-Key. Staff can also refund from the Business Portal. Payments taken on B-Gateway before you switch stay refundable in B-Online.
See Payment Actions.
Skip this if you only send TransType 1 without an AuthCode. Create the session with type: "PRE_AUTHORISATION"; the checkout is unchanged and the funds are held (no currency conversion on pre-authorisations). Capture with POST /v1/transactions/{id}/capture and {"amount": n}: any amount up to the authorised total, but exactly once. The remainder is released, so capture the final amount; this also replaces the partial reversal (TransType 4). To cancel, refund the uncaptured payment; untouched holds expire on their own. See Payment Actions.
Batches, transaction and settlement reports, telephone or keyed orders, Dynamic Descriptors and detail or itinerary data each have their own answer on the new platform, and some depend on your set-up. If you use any of them, contact Teya support with your specific case before you migrate, so we can check it with you. The B-Gateway reference lists every operation and where it lives now.
Test in staging with the cards on the Testing page; frictionless, challenge and declined scenarios are all there. If your development machine isn't publicly reachable, tunnel it so staging webhooks can reach you.
Definition of done
COMPLETED / SUCCESS401 by refreshing the tokensave_payment_method and an ACCOUNT_VERIFICATION both return a token_id in the webhook; a merchant-initiated charge on that token succeedsGo live
.xyz → .com) and, for Embedded Components, remove developmentMode.getVirtualCard and batch code. Remove stored card numbers and card data from your systems and logs. Teya retires the B-Gateway usernames and passwords for you; there is nothing to request.| Symptom | Likely cause | Fix |
|---|---|---|
| Charged amount is 100× too big (ISK) | B-Gateway's two-implied-decimals convention carried over | Divide legacy ISK amounts by 100 |
422 creating a session | Numeric currency code (352) or a non-integer amount | Alphabetic currency; integer amount.value in minor units |
401 on API calls | Access token expired; unlike a B-Gateway password it is short-lived | Refresh from expires_in; retry once on 401 |
403 | Missing scope, e.g. transactions/online/create for saved-card charges | Add the scope to the token request (Step 1) |
409 | Same Idempotency-Key, different body | Unique key per order attempt or billing period; reuse only for retries of the identical request |
Payment succeeds but no token_id in the webhook | save_payment_method missing, or the store was created less than 48 hours ago and tokenization enrolment is still pending | Send save_payment_method: true; wait for enrolment, then contact support if it persists |
400 charging a 9910… value as TOKEN | A legacy virtual card number sent as a Teya token | Keep charging it on B-Gateway until your stored cards are moved, then use LEGACY_TOKEN. See Stored cards |
| A refund can't find the payment | Legacy DateAndTime + RRN used as the reference | Refund by transaction_id; refund B-Gateway payments in B-Online |
| Webhook never arrives | URL not public, no valid certificate, auth on the endpoint, or non-200 responses | Public HTTPS, no auth, return 200 after processing |
| Signature verification fails | Parsed body re-serialised, or the wrong key | Verify the raw body; use the key issued for this webhook in this environment |
| Payment succeeded but not in the Business Portal | Wrong environment | Staging payments are in business.teya.xyz, production in business.teya.com |
For partners and platforms that create payment URLs in advance with the Ticket API on behalf of the merchants they serve. This guide moves that flow to Pay By Link.
The Ticket API runs on Teya's legacy platform, which is retiring in early 2027. Its replacement is Pay By Link: the same idea — a URL you create and share, opening a Teya-hosted payment page — on the new platform.
Business Portal / Teya app Recommended for a handful of links a week — staff enter the amount, a description and your reference, and the Portal emails or texts the link to the customer. Status, resend, cancel and refund are all in the Portal. No developer needed: the migration is a walkthrough of Pay By Link in the Portal.
Pay By Link API — if your systems generate the URLs (invoicing runs, booking confirmations, reminders), your developer replaces each Ticket API call with one Payment Links call and handles a webhook. See the developer guide below.
Both work side by side on the same store.
Portal only: done after a team walkthrough. API: days including testing, not weeks — a fraction of a checkout migration.
```admonition icon: fas fa-paper-plane title: What to send your developer content: (1) The link to this page. (2) Which systems create tickets today, and where the paid confirmation needs to land (invoicing, CRM, booking). (3) Whether links should expire, be resent, or accept Apple Pay. ```Replace each Ticket API call with one Pay By Link call, and the legacy paid confirmation with a signed webhook.
Today (Ticket API)
After (Pay By Link)
POST /v2/payment-links. Teya returns a payment_link URL and a payment_link_id.customer.email and let Teya send it.payment.succeeded.v1 webhook carrying payment_link_id, merchant_reference and transaction_id. You mark the invoice paid.Biggest change: confirmation arrives as a signed webhook you verify server-side, not through a legacy callback.
Each merchant's client_id, client_secret, store_id and payment_webhook_public_key arrive through the merchant registration flow when the merchant connects Teya to your platform. For larger existing bases, agree a batch approach with your Teya partner contact. One set of credentials per store — never shared.
Staging api.teya.xyz / id.teya.xyz / business.teya.xyz; production .com — full table on the Pay By Link API page. Scopes: payment-links/create payment-links/id/get payment-links/id/update, plus refunds/create if you refund by API. Cache tokens by expires_in (currently 15 minutes in production, 24 hours in staging).
The webhook registered at merchant registration receives that store's payment events; its payment_webhook_public_key is the key you verify with. See Webhooks.
Search for the Ticket API host and paths, the module that creates tickets, and the code that consumes the legacy paid confirmation. Each ticket-creation call site becomes one POST /v2/payment-links; the confirmation consumer becomes your webhook handler.
amount.value is an integer in minor units — €54.43 is 5443; ISK has no subunit, so 100 ISK is 100Idempotency-Key on every create; a retry with the same key returns the same link| Ticket API today | Pay By Link | Notes |
|---|---|---|
| Ticket-creation call | POST /v2/payment-links | Or Create Payment Link in the Portal |
| Legacy credentials / per-request signature | OAuth client_credentials bearer token | Scope payment-links/create |
| Ticket URL | payment_link | Deliver it as today, or set customer.email / phone_number |
| Ticket identifier | payment_link_id | Store it against the invoice or order |
| Amount and currency | amount.value, amount.currency | Minor units |
| Your order / invoice reference | merchant_reference | Returned in the webhook and on lookup |
| Description shown to the customer | line_items | Portal: Description |
| Ticket validity / expiry | expires_at | Extend with PATCH /v2/payment-links/{id} |
| Cancelling a ticket | PATCH with a past expires_at | Portal: Cancel |
| Legacy paid confirmation | payment.succeeded.v1 webhook | Signed; verify with the per-webhook key |
| Checking whether a ticket was paid | GET /v1/payment-links/{id} | Scope payment-links/id/get |
| Refund | POST /v3/refunds | Uses the webhook's transaction_id; or the Portal |
| 3D Secure | Handled on the hosted page | Nothing to build |
cURL only. Other languages: see the Pay By Link API page.
Replaces the legacy per-request credentials. One token serves every call until it expires.
```bash curl -X POST https://id.teya.xyz/oauth/v2/oauth-token \ -H "Content-Type: application/x-www-form-urlencoded" \ -d "grant_type=client_credentials" \ -d "client_id=$TEYA_CLIENT_ID" \ -d "client_secret=$TEYA_CLIENT_SECRET" \ -d "scope=payment-links/create payment-links/id/get payment-links/id/update refunds/create" ``` ```json { "access_token": "eyJhbGciOiJSUzI1NiJ9...", "token_type": "Bearer", "expires_in": 900 } ```expires_in runs out.Full reference: Pay By Link API
Replaces the ticket-creation call. Send the amount, your reference and, if Teya should deliver the link, the customer's details.
```bash curl -X POST https://api.teya.xyz/v2/payment-links \ -H "Authorization: Bearer $ACCESS_TOKEN" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: $(uuidgen)" \ -d '{ "amount": { "value": 5443, "currency": "EUR" }, "merchant_reference": "INVOICE-2026-0117", "expires_at": "2026-10-31T23:59:59Z", "line_items": [ { "description": "Invoice 2026-0117", "quantity": 1, "unit_price": 5443 } ], "customer": { "name": "Jan Novák", "email": "jan@example.com" } }' ``` ```json { "payment_link_id": "20eb276c-5b6b-4474-a5a1-49bcd6193148", "payment_link": "https://checkout.teya.com/pbl/20eb276c-5b6b-4474-a5a1-49bcd6193148" } ```payment_link_id against the invoice before you send anything.expires_at to the invoice due date; an expired link shows the customer an error.metadata, receipt emails, supported_payment_methods): see the reference.Full reference: Pay By Link API · API Reference
Replaces the legacy paid confirmation. Verify the x-teya-signature header (SHA256withRSA, Base64) over the raw body with the webhook's public key, then match data.payment_link_id or data.merchant_reference to your invoice and mark it paid.
200 quickly, process asynchronously, and treat a replayed event as already handled.transaction_id: refunds and receipts need it.Full reference: Webhooks — payload fields and the signature validator
Status lookup. GET /v1/payment-links/{payment_link_id} returns the link's status, amount, merchant_reference and expires_at — use it before resending a reminder. The webhook remains the source of truth. To extend or cancel, PATCH /v2/payment-links/{payment_link_id} with a new expires_at (the only updatable field). See Pay By Link API.
Refunds. POST /v3/refunds with the webhook's transaction_id; partial and multiple refunds are allowed up to the original amount (scope refunds/create). Or refund in the Business Portal with no code. See Payment actions.
Receipts. The customer receives a receipt automatically. To send one yourself: POST /v1/transactions/{transaction_id}/receipts.
Pre-authorisation. Not available on payment links — transaction_type is SALE. To reserve funds and capture later, use Hosted Checkout with a PRE_AUTHORISATION session.
Test in staging with the cards on the Testing page. Definition of done:
Go live: production credentials and webhook (new key), swap .xyz hosts for .com, send yourself one real link and pay it, run the Ticket API in parallel for a day or two, then retire the legacy code and ask your Teya contact to decommission the old credentials.
| Symptom | Cause | Fix |
|---|---|---|
403 on create | Token lacks payment-links/create | Request the scopes above when fetching the token |
422, amount rejected | Major units sent (54.43) | Send minor units (5443); ISK at face value |
| Duplicate links after a retry | No Idempotency-Key | One key per invoice attempt, reused on retry |
| Signature never verifies | Body re-serialised, or wrong key | Verify the raw bytes with this webhook's key |
| Invoice never marked paid | Still waiting on the legacy callback | Confirmation now arrives only by webhook — check the endpoint is public HTTPS and returns 200 |
| Customer reports "link expired" | expires_at passed | PATCH a later expires_at, or create a new link |
For partners whose merchants store customers' cards on the legacy platform and charge them again later — subscriptions, instalments or saved cards. This is the second part of a wider migration: first your integration moves to the new platform using the guide for it, then — once that integration is built and live for a merchant — that merchant's stored cards move too, without any customer re-entering a card, in four phases that you control per merchant.
Follow this guide if your systems hold token values received when customers saved their cards on the legacy platform, and you charge those tokens again. The stored values look like one of these:
tm_ followed by letters and digits, e.g. tm_aBcDeFgHiJkLmNoPqRsTuVwXyZ012345991001, e.g. 9910012837465190It covers those tokens, the payments you make with them, and how new cards get saved from now on. It does not cover migrating your checkout itself — that is the first part of the project, with its own guide, and it must be finished before the token migration starts.
```admonition icon: fas fa-route title: Where this sits in the migration content: Step 1 — build and go live with your new-platform integration (Hosted Checkout, Embedded Components or the direct API) using the guide for your legacy integration: find it here. Step 2 — this guide: move the stored cards. Everything below assumes step 1 is done; if it isn't yet, start there and come back when your new integration is live. ```The legacy platform, including its token store, is retiring in early 2027. Tokens minted on it cannot be charged on the new platform directly — but they don't need to be re-collected either: Teya imports your stored cards, and each legacy token upgrades itself the first time you charge it.
Four phases, in this order. The order is what makes it safe: at some point Teya takes a copy of your stored cards from the legacy platform, and any token created after that copy would be missing from it. So the first thing to stop is the creation of legacy tokens; the last thing to stop is their use.
Effort: a small development project — one new-card path, a one-line routing rule, and one field change for the cut-over. The phases can be weeks apart; there is no pressure on how long Phase 2 lasts.
```admonition icon: fas fa-paper-plane title: Planning this across a merchant base content: The phases run per merchant, and the import in Phase 3 is per merchant too. Agree phase dates with your Teya partner contact in batches, and make sure Phase 1 is complete for a merchant — every store, every code path, every background job — before you confirm it for import. ```The four phases in detail: what to build, the one routing rule, and what happens on the first charge of a legacy token. Token payments themselves are documented on Charge a Saved Payment Method; this page only covers what is specific to migrating.
POST /v3/transactions/online — the direct API used for token payments. Read Card on File and Charge a Saved Payment Method first; the requests below are those pages' requests with one different payment method.Phase 3 can only start after Phase 1 is genuinely done: a legacy token created after the export will not be in the import, and in Phase 4 it fails with an explicit "not migrated" error rather than falling back to the legacy platform. Your Teya contact also provides test-environment legacy-token fixtures and confirms when the legacy-token payment method is enabled for your environment.
Search your codebase for the places that:
/api/token/single, /api/token/multi, SecurePay tokenisation) — Phase 1's scopetm_, 991001) — Phase 2's routing and Phase 4's scopestore_id it was migrated for.token_response.token_id on every successful charge — it is the customer's permanent Teya token.| Legacy platform | New platform | What it means for you |
|---|---|---|
Multi-use token tm_… / virtual card 991001… | Teya token — a UUID in token_response.token_id | Charged with payment method TOKEN. Legacy values are accepted once more, as payment method LEGACY_TOKEN, to convert them (Phase 4). |
POST /api/token/single, /api/token/multi | store_payment_method: true on a SALE or a zero-amount ACCOUNT_VERIFICATION transaction | Tokenisation happens inside the payment; no separate call. See Card on File. |
POST /api/payment with a multi-use token | POST /v3/transactions/online with payment_method.type: "TOKEN" | Same endpoint as every other direct-API payment. See Charge a Saved Payment Method. |
| Recurring flag on the payment | initiator (MERCHANT / CUSTOMER) + recurring_payment_type (RECURRING / INSTALLMENT / CARD_ON_FILE) | MERCHANT = cardholder not present, no 3DS. CUSTOMER = cardholder present, 3DS applies and may challenge. |
| Token deletion | DELETE /v1/tokens/{token_id} → 204 | Delete the token when the customer removes a saved card. |
| Your own token database | Your own token database — now holding a Teya token (UUID) or, temporarily, a legacy value | The routing rule below keys on which one the customer has. |
What: stop creating tokens on the legacy platform. From this day, when a customer saves a card, save it on the new platform and store the Teya token you get back.
Why: every card saved from now on is already migrated, and the export in Phase 3 will be complete.
A token is created by setting store_payment_method: true on a transaction — as part of a sale (type: "SALE"), or without charging the customer (a zero-amount type: "ACCOUNT_VERIFICATION", for an "add a payment method" flow). Either way the response carries a token_response:
Store token_id against the customer. brand and last4 are for what you show them ("Visa ending 1234").
Things to get right:
token_status: "FAILED" with a failure_reason. Treat it as "saving the card didn't work", not as a payment problem.Full reference: Card on File — storing the card.
What: existing legacy tokens keep being charged where they always were — on the legacy platform, unchanged. Customers who saved a card since Phase 1 are charged on the new platform, as an ordinary token payment.
Why: nothing is at risk while the import is prepared, and every card saved during this phase is already on the new platform.
```bash curl -X POST https://api.teya.com/v3/transactions/online \ -H "Content-Type: application/json" \ -H "Authorization: Bearer $ACCESS_TOKEN" \ -H "Idempotency-Key: $(uuidgen)" \ -d '{ "store_id": "5bca0d99-3f47-4d91-9372-cf12b8ec50db", "type": "SALE", "initiator": "MERCHANT", "amount": { "value": 10000, "currency": "GBP" }, "merchant_reference_id": "order_ABC123", "payment_method": { "type": "TOKEN", "token": { "token_id": "1e2d4f80-9c3e-4f5b-83aa-9e8d2b6a6c49", "recurring_payment_type": "RECURRING" } } }' ```Your billing engine's routing rule for this whole phase is one line:
``` for each due charge: customer has a Teya token (UUID) → new platform, payment method TOKEN customer has a legacy value → legacy platform, unchanged ```Two fields describe why you are charging a stored card:
| Field | Values | Meaning |
|---|---|---|
initiator | MERCHANT | The cardholder is not present — a recurring charge, an instalment, or a merchant-initiated card-on-file payment. No 3DS/SCA is performed. |
CUSTOMER | The cardholder is present (e.g. clicking "pay with saved card" on your site). 3DS/SCA is performed and may require a challenge. Default if omitted. | |
recurring_payment_type | RECURRING, INSTALLMENT, CARD_ON_FILE | The type of stored-card usage. CUSTOMER-initiated payments support only CARD_ON_FILE; MERCHANT-initiated payments support all three. |
Things to get right: amount.value is in the smallest currency unit (100 ISK = 100, 100 EUR = 10000). Send an Idempotency-Key per charge attempt.
Full reference: Charge a Saved Payment Method.
Once you've confirmed to your Teya contact that no new legacy tokens are being created, Teya takes the file of your stored cards from the legacy platform and imports it into the new one. Your customers never re-enter a card and you never touch card data. Keep the Phase 2 routing unchanged while the import runs; Teya confirms when it is complete.
What: with the import confirmed, retire the "legacy platform" branch of your routing. A legacy token is now charged on the new platform — same endpoint, same fields — with one different payment method:
```json "payment_method": { "type": "LEGACY_TOKEN", "legacy_token": { "token_id": "tm_aBcDeFgHiJkLmNoPqRsTuVwXyZ012345", "recurring_payment_type": "RECURRING" } } ```What happens on first use. The first time each legacy token is charged on the new platform, it is upgraded in-line, inside that payment:
Two properties of this design are worth knowing: the imported card record is never used to pay directly — it exists only to mint the network token, once; and if any step of the conversion fails, the payment fails with an explicit error (below) with nothing half-saved, so a later retry starts clean. Expect the first charge of each token to take a little longer than usual; every later charge skips the conversion entirely.
What comes back — and the one thing you must do with it. A successful charge returns 201, and the response carries the customer's new Teya token:
The same token_response also appears on the transaction lookup and on the payment.succeeded.v1 webhook. Store the token_id, and from this customer's next charge on send it as payment_method.type: "TOKEN". Your routing rule becomes:
Things to get right:
token_id — a lagging deploy or a webhook/response race does no harm.initiator: "CUSTOMER") may return 202 with redirect_html for a 3DS challenge — handle it exactly as on Charge a Saved Payment Method. The conversion happens before the challenge, and the completed payment carries the same token_response.All errors use the API's standard shape — a code and a human-readable description. A payment that fails this way is never sent for authorisation: the customer is not charged and no webhook is emitted.
| HTTP | Description you'll see | Meaning | What to do |
|---|---|---|---|
400 | Legacy token must be either a tm_ prefixed token or a 16-digit virtual card PAN starting with 991001 | The value doesn't match the two valid shapes | Fix the stored value or the lookup that produced it — this was never a valid token. |
400 | Legacy token has not been migrated to a new token. Please contact support. | No imported record for your store + this token | Either the import (Phase 3) hasn't completed for this store, or the token was created after Phase 1 should have ended. Don't retry in a loop; flag it and check with your Teya contact. |
400 | Legacy token payment method is not enabled | The legacy-token flow isn't switched on for your environment yet | Ask your Teya contact about rollout status. |
500 | Error resolving legacy token | The first-use conversion failed part-way | Transient — safe to retry later. Nothing was half-migrated. |
The signal to watch at cut-over is the ratio of legacy-token failures to attempts. A burst of "not migrated" errors on day one almost always means Phases 3 and 4 happened in the wrong order — or that something, somewhere, is still creating legacy tokens.
TOKEN charge. Then a full billing cycle, watching the failure ratio.The questions the migration guides don't answer step by step: timelines, what changes for the merchants you serve, migrating a merchant base, and what is different on the new platform. For the technical migration itself, start at the overview.
SecurePay, the RPG payment API, the B-Gateway web service and the Ticket API keep working as normal today. Onboarding to them stops soon, and they are retired in early 2027. You will be given notice before anything changes for your customers, and your Teya contact can tell you where you stand at any time.
Any merchant still routed through your legacy integration when the platform is retired will stop being able to take payments through your platform. Teya will contact merchants directly about the retirement, so merchants will also be asking you what your plan is.
We will contact you before your integration is affected — but don't wait for that: the work is the same now as later, and now is calmer.
There is no Teya fee for migrating, and your pricing agreement with Teya is unchanged — it is not tied to which platform your integration uses. The only prerequisite is that Teya online payments are enabled on your store, which your Teya contact arranges.
It depends on the path. A SecurePay redirect integration moving to Hosted Checkout is a small development project — days including testing, not weeks. Embedded Components and RPG integrations are a little more, mainly frontend work. B-Gateway integrations are a small development project too, and end with less code than you have today. Ticket API users often need no development at all. Stored cards and recurring payments are a separate, phased track that runs alongside normal billing cycles — see the stored cards guide.
Your Teya merchant account and settlement stay the same. What changes is how your integration authenticates: instead of a SecurePay secret key, an RPG private key or B-Gateway credentials, each store gets OAuth API credentials (a Client ID and Client Secret). For merchants you already serve on RPG you exchange the existing key; for others, credentials arrive through the registration flow — see credentials for existing merchants. Your store needs Teya's online payments enabled; if credential creation reports that no compatible store was found, ask your Teya contact.
Ask whoever built your site where the customer types their card number. A Teya/Borgun page means SecurePay redirect; a frame inside your page means SecurePay embedded; your own form means a direct API. For a direct API, check what your server sends to borgun.is: JSON to /api/payment is RPG, XML such as getAuthorization to gateway01.borgun.is is B-Gateway. The overview turns the answers into a guide. If nobody knows, your Teya contact can look up how your payments arrive today.
Yes, and the guides recommend it: keep the legacy integration live for a day or two after you switch, so rolling back is a configuration change. Both settle to the same merchant account. There is no time limit on parallel running before the retirement date.
Everything is rehearsed in Teya's staging environment with a development store and test cards, including 3D Secure challenges, declines, refunds and webhooks. Nothing reaches your production account until you create production credentials and switch hosts.
On Hosted Checkout the experience is the same shape as SecurePay: click Pay, enter the card on a Teya page, come back. Differences they may notice are improvements — a declined card lets them try another instead of ending the checkout, Apple Pay appears where the device supports it, and foreign cardholders may be offered to pay in their own currency. With Embedded Components the card fields sit inside your own checkout, so the visible change is that they no longer leave your site.
You delete it. Authentication runs automatically inside Hosted Checkout and the Embedded Components — enrollment, the bank challenge and validation are handled for you, with no separate calls and nothing to certify. The only place you still see 3D Secure explicitly is customer-present charges on saved cards through the direct API, where a challenge may be returned for you to display.
This also applies if you run 3D Secure through B-Gateway's get3DSAuthentication or a third-party 3D Secure provider: the connection is no longer needed. On the new platform every customer-present payment goes through 3D Secure, and charges without the customer are made on a card they saved with you, as merchant-initiated payments. See the B-Gateway guide.
Yes. Refund legacy transactions in B-Online exactly as you do today — it keeps working alongside your new integration and stays available until the legacy platform is retired. Refunds of payments taken on the new platform are made in the Business Portal or by API. Your legacy transactions are also visible in the Business Portal, and stay there.
Yes. Your legacy reporting stays available, read-only, until the legacy platform is retired. Your legacy transactions are also visible in the Teya Business Portal and remain there indefinitely, alongside new-platform payments — which appear in the Portal from your first live transaction.
B-Gateway's transaction list, settlement, chargeback, dispute and fraud reports have no one-to-one API replacement. For a single payment, look up its checkout session by ID; for everything else, use the Business Portal. There are no batches on the new platform, so batch numbers disappear from your records: key them on the transaction_id instead. If a finance or reconciliation system depends on these reports, contact Teya support with your specific case before you migrate so we can check it with you. Each report is listed on the B-Gateway reference.
Yes. Nobody re-enters a card: Teya imports your stored cards, and each legacy token upgrades itself the first time you charge it on the new platform. The order of steps matters — read the stored cards & recurring guide before changing anything about how cards are saved.
This includes virtual card numbers from B-Gateway's getVirtualCard.
Once your parallel-running window is over, remove the legacy code; Teya retires the old SecurePay, RPG or B-Gateway credentials for you — nothing to request. Branding settings do not carry over: logo, checkout and receipt messages and legal URLs are configured once per store in the Business Portal.
Disputes are handled against your merchant account, not the integration, so a chargeback on a payment taken through SecurePay is handled the same way after you migrate.
Evidence for legacy transactions remains retrievable — they stay visible in the Business Portal.
Use merchant_reference for your order number, and keep writing your legacy order ID into metadata during the transition if your back office keys on it. Every new-platform payment has a transaction_id that appears in the webhook, the session lookup and the Business Portal — store it on the order.
Amounts are in the smallest unit of the currency: €54.43 is 5443, £9.99 is 999. ISK has no subunit, so 100 ISK is simply 100. This is the single most common bug in migrations from SecurePay, which took a decimal string.
The platform sends a signed webhook for successful payments (payment.succeeded.v1). Failures and abandoned checkouts are covered by looking the session up by its ID, which also tells you whether a customer who never returned to your site did in fact pay. See Webhooks.
In the Business Portal (and the Teya app), alongside your in-store payments if you have Teya card machines. Refunds can be issued there without a developer.
Settlement does not change: same merchant account, same payout timing and statements. New-platform transactions simply appear in the Business Portal as well.
Refunds are full or partial, and can be repeated up to the original amount. A payment is captured at most once per authorisation, and the remainder is released. There is no separate "cancel": a refund on an uncaptured payment reverses the authorisation. See Payment Actions.
Yes — and if you were on SecurePay this is new. Create the session with type PRE_AUTHORISATION and capture later, including a partial amount. Pre-authorisations are currently available for certain tourism-related business activities; if your business needs them for another use case, contact your Teya account manager.
Yes, automatically: when a cardholder's currency differs from the transaction currency, the checkout offers to pay in their own currency. There is nothing to implement. DCC is not offered on pre-authorisations.
The same ISO currency codes as before (ISK, EUR, GBP, CZK, HUF, USD and others). HRK is no longer used; Croatia settles in EUR. The checkout language is auto-detected from the customer's browser or set per session with a BCP 47 code such as is-IS or cs-CZ.
Checkout languages include English, Icelandic, Czech, Slovak, Croatian and Hungarian, among others.
Card data is entered on Teya-hosted pages or inside Teya-hosted iframes, so your systems never see it and you stay in the lightest PCI category (SAQ A). Per-request signatures (checkhash) are replaced by short-lived access tokens, and payment confirmations are RSA-signed webhooks you verify with a public key rather than a shared secret. Sending raw card data to Teya from your own servers is restricted to merchants with PCI DSS (SAQ D) evidence.
If you use B-Gateway today, card numbers pass through your own servers. Moving to Embedded Components or Hosted Checkout takes them out entirely, which is usually the biggest reduction in compliance work of any migration path.
Some B-Gateway features work differently on the new platform or depend on your set-up: batches (settlement is now automatic), partial reversals (capture the final amount on a pre-authorisation, or refund the difference), Dynamic Descriptors, invoice detail data (sendDetailData), passenger itinerary data, batch authorisations, and telephone, keyed (B-Online) or card-reader payments. If you use any of them, contact Teya support with your specific case before you migrate. The B-Gateway reference lists every operation and where it lives now.
No. Use the official list on the Testing page; it covers frictionless and challenge 3D Secure, declines and specific error cases.
For merchants on RPG: exchange each merchant's RPG private key for new credentials with one API call per store — see RPG Credentials Migration. For merchants on SecurePay: register each merchant through the registration flow, where the merchant approves the connection once from their Teya account. To migrate a large merchant base, talk to your Teya partner contact about the approach.
Yes. Credentials are per store and every merchant switches independently, so you can move a handful, verify, and continue. Keep the legacy route available per merchant for a short rollback window.
Teya notifies every legacy merchant directly about the retirement. The preparation is managed by you; merchants may need to reconnect their store or approve the connection between your platform and their Teya account when the switch happens, so tell them what to expect. You know your merchants and your migration plan, so you decide how and when to communicate with them — most partners tell merchants in advance that the migration is handled by the platform, so nobody acts on Teya's notice by looking for a new provider. If you would like support or guidance with that communication, ask your Teya partner contact.
Every merchant that pays through your legacy integration is on it — your own merchant records are the list. Use them to plan and track the migration store by store. If you need help identifying stores or reconciling them with Teya's records, ask your Teya partner contact.
Each integration is migrated by whoever built it: you migrate your platform's integration for that merchant's store, the merchant's own developer follows the Businesses guides for theirs. Credentials are per store, so both can proceed independently.
No certification. If your merchants integrate through a plugin, Teya assigns a plugin_type for you to send with each credential exchange or registration; it identifies your integration for support and debugging.
The four phases run per merchant, and the import in Phase 3 is per merchant. Agree phase dates with your Teya partner contact in batches, and confirm a merchant for import only once Phase 1 is complete for every store and code path. See the stored cards guide.
Any merchant still on your legacy integration when the platform is retired will stop being able to take payments through it. Agree a migration plan and completion date with your Teya partner contact; we will contact you before any of your merchants is affected, and work with you to avoid it.
```admonition icon: fas fa-life-ring title: Question not answered here? content: Contact your Teya partner contact. For integration questions, see Support. ```