# Request an access token Source: https://developer.hellgate.io/platform/api-reference/oauth2/request-an-access-token /platform/openapi.yaml post /oauth2/token Exchange your client ID and client secret for an OAuth2 access token using the client-credentials grant. Send the credentials with HTTP Basic authentication and the form fields as `application/x-www-form-urlencoded`. # Overview Source: https://developer.hellgate.io/platform/api-reference/overview The Authentication API issues OAuth2 access tokens for Hellgate Cloud Platform services. The Authentication API issues the **OAuth2 access tokens** used to call Hellgate Cloud Platform services. You exchange a **client ID** and **client secret** for a token using the **client-credentials** grant, then send the token as a bearer token on each request. The platform-level OAuth2 API. Duplicate the **Dedicated instance — template** environment and fill in your client credentials. **OAuth2 → Request an access token** is the token endpoint documented here, sent as a real request; the product collections in this workspace get their tokens the same way, from a pre-configured Authorization tab. ## Token endpoint The token endpoint is hosted at `https://auth.eu1.hellgate.cloud`. ## Authentication Send your client ID and client secret to the token endpoint using **HTTP Basic** authentication. The **Request an access token** operation describes the request and response in full. Tokens are scoped to an **audience** — the service instance they may call — and carry the **scopes** that gate individual endpoints. The `audience` and `scope` parameters are both required on every token request; there are no defaults, and each must fall within the limits provisioned on your client. For how a specific product validates the token and which scopes and audience it requires, see that product's access guide — for example, [Specter Authentication](/products/specter/authentication). ## Data Classification Fields that carry regulated data are marked with a classification pill next to the parameter name in this reference. The marker tells you what kind of data the field holds, so you can see at a glance which parts of a payload attract handling obligations before you write any code against them. | Marker | The field carries | | - | - | | pci-pan | The primary account number — a card number, or a value from which one can be reconstructed. | | pci-sad | Sensitive authentication data — security codes and the equivalent authentication values issued for a token. | | pii | Personal data under the GDPR — any information relating to an identified or identifiable person, not only the cardholder. | Most fields carry no marker at all. The absence of one is a statement that the field holds none of the categories above — not that the field has yet to be reviewed. Masked and truncated card numbers are the common example: `411111 •••••• 1111` identifies nobody on its own, so it carries no marker. The marker is also a gate, not just a label. Sending values into `pci-pan` or `pci-sad` fields requires your PCI DSS certification on file at **SAQ-D** or a full Report on Compliance (RoC). Sending [personal data](https://eur-lex.europa.eu/eli/reg/2016/679/oj) into `pii` fields requires a data processing agreement (DPA) between us. Contact [support@hellgate.io](mailto:support@hellgate.io) to put either in place or to learn more. The same markers are used on the other Hellgate Cloud Platform references that carry them, so a marker means the same thing wherever you meet it. # Authentication Source: https://developer.hellgate.io/platform/authentication Hellgate Cloud Platform services authenticate with API keys, OAuth2 bearer tokens, or mTLS. This page covers OAuth2 access tokens. Hellgate® Cloud Platform services authenticate with one of three mechanisms — **API keys**, **OAuth2 access tokens** (bearer JWTs), or **mTLS**. OAuth2 access tokens are the standard mechanism for the services running on dedicated infrastructure; Commerce authenticates with an API key. | Product | API key | OAuth2 | mTLS | | - | - | - | - | | [Guardian](/products/guardian/overview) | ✓ deprecated | ✓ | — | | [Specter](/products/specter/overview) | — | ✓ | — | | [Link](/products/link/overview) | — | ✓ | — | | [Commerce v1](/products/commerce/v1/overview) | ✓ | — | ✓ | | [Commerce v2](/products/commerce/v2/overview) | ✓ | — | — | ## OAuth2 access OAuth2 access is a platform-level capability shared by every service that supports it. This page covers OAuth2 access tokens only — for API keys and mTLS, see the relevant product's documentation. ### Obtain client credentials Client credentials are issued by Hellgate. Contact Hellgate support to request them. Each credential is scoped to a specific service (its **audience**) and is provisioned with a defined set of **maximum scopes** based on your request. A token can never exceed the audience and scopes provisioned on the credential it is issued from. ### Request a token Exchange your client credentials for an access token at the OAuth2 token endpoint: ``` https://auth.eu1.hellgate.cloud/oauth2/token ``` You authenticate to this endpoint with HTTP Basic authentication, sending your client ID and client secret. See the [Authentication API](/platform/api-reference/overview) reference for the full request and response. ### Audiences A token names one or more **audiences** — the service instances it is allowed to call, carried in the token's `aud` claim. A service rejects any token whose audience does not name it, so a token issued for one instance cannot be used against another. Pass audiences in the `audience` request-body parameter as a space-delimited list. There is no default audience: the parameter is required on every token request, and each value must be within the audiences provisioned on your credential. A request without a valid audience is rejected. To target more than one service with a single token, list each audience (see [Composition](#composition)). ### Scopes You must request **scopes** through the `scope` parameter on every token request, as a space-delimited list. The requested scopes must be a subset of the maximum scopes provisioned on your credential — there are no default scopes. The available scopes depend on the service; see, for example, [Specter scopes](/products/specter/authentication#scopes). ### Composition When a token is used transitively across composed services — for example, Specter composing with Link — it must name every service in the composition and carry the scopes each one requires. List all the audiences — each the **instance name** of a service — together with the union of their scopes when you obtain the token (for example, `audience=my-specter-instance my-link-instance`). ### Use the token Send the access token in the `Authorization` header on every API request: ```http theme={null} Authorization: Bearer ``` The token carries the scopes that gate access to individual endpoints and expires 30 minutes after it is issued (`expires_in` is `1800`). Cache and reuse it until shortly before it expires, then request a new token on expiry or on a `401` response. # Cloud Platform Updates Source: https://developer.hellgate.io/platform/changelog New releases and improvements of the Cloud Platform. ## Data Classification Markers in the API References Fields that carry regulated data now show a classification marker next to the parameter name throughout the API references. `pci-pan` marks a card number, or a value from which one can be reconstructed; `pci-sad` marks sensitive authentication data such as security codes and cryptograms; `pii` marks personal data under the GDPR. You can see which parts of a payload attract handling obligations before writing any code against them. Most fields carry no marker, and that absence is deliberate: it states the field holds none of those categories, not that nobody has looked yet. Masked and truncated card numbers are the common case — `411111 •••••• 1111` identifies nobody on its own, so it carries no marker. The marker is also a gate. Sending values into `pci-pan` or `pci-sad` fields requires your PCI DSS certification on file at SAQ-D or a full Report on Compliance; sending personal data into `pii` fields requires a data processing agreement. Contact [support@hellgate.io](mailto:support@hellgate.io) to put either in place. The full legend is on the [Authentication API overview](/platform/api-reference/overview#data-classification). Nothing about any endpoint changed — this is documentation only. ## Hellgate Web SDK (v3.11.0) ### Central Control Over Card Saving Decide once, when you create a session, whether cards are saved — always, never, or left to the cardholder — and the SDK enforces that policy at checkout. Your card-retention rules stay consistent across every page that embeds the SDK, with nothing extra to implement in each frontend. The SDK also tells you which policy is active, so you can show a "Save my card" checkbox only when the cardholder really has the choice. ### Improvements and bugfixes * A cardholder's decision not to save their card is now always honored. Previously, in some integrations, declining to save a card could still result in a saved card. ## Link documentation now public [Link](/products/link/overview) — the protocol-abstraction runtime of the Composable Payment Architecture — now has public documentation. Link is the platform's PSP and provider connectivity layer: define a versioned API contract (a **protocol**) once and bind it to any provider through declarative **backends**, so integration becomes configuration, not code. It is the component [Commerce](/products/commerce/v2/overview) routes through to reach acquirers and PSPs, and that [Specter](/products/specter/overview) reaches through to call external risk engines. Explore the [overview](/products/link/overview), [core concepts](/products/link/concepts/protocols), and the [API reference](/products/link/api-reference/getting-started). ## Hellgate Web SDK (v3.10.0) ### Cardholder-Aware Token Lifecycles Align token retention with what your cardholder actually agreed to. A new optional `ephemeral?: boolean` argument on `process()` and `tokenize()` defaults to `true`, meaning the cardholder is *not* remembered. Flip it to `false` only on explicit consent — for example, when a customer ticks a "Save my card" box at checkout. ### Smarter Scheme Validation Allowed card schemes configured on the session now flow through to client-side card number validation. Cardholders see scheme errors immediately, before the request ever leaves the browser. ### Improvements and bugfixes * Empty `card_schemes` configuration now correctly allows all schemes. * Separate expiry month/year inputs now treat `'1'` as a valid month and properly reject past-expiry dates. * Demo: a new "Save my card" checkbox in the tokenization demo wires straight into the new ephemeral toggle so you can try it end-to-end. ## Hellgate Web SDK (v3.9.1) Security improvements. ## Hellgate Web SDK (v3.9.0) Added support to split the expiriy input fields in the Web SDK. ## Hellgate Web SDK (v3.8.1) September cleanup and maintenance release for our SDKs. ## Hellgate Web SDK (v3.8.0) Validate CVC length based on card scheme in multi-line forms. ## Hellgate Android SDK (v1.1.0) The Android SDK now encrypts the full payload — including any additional data — into a single JWE and returns it to your app, instead of calling the tokenization backend directly. This simplifies your integration and keeps sensitive data encrypted end to end. ### Improvements and bugfixes * Additional cardholder data is now encrypted as part of the JWE token. * Hellgate error responses now surface with clearer, more actionable detail. * Removed support for tokenizing through an external token provider. * Updated environment URLs. ## Hellgate Web SDK (v3.7.0) Add 'onSessionComplete' handler to Hellgate® client with result data. ## Hellgate Web SDK (v3.5.3) Improved the SDK with small but important fixes: * Fix masked input behaviour * Reject cardHandler.process() promise if BE responds 40x / 50x status code * Await cardHandler.process() throws exceptions if card date is not complete or invalid * Show full CVC code up to 4 digits * Card scheme validation by default * UnionPay card number input Added some new features: * Encrypt additional\_data payload on tokenize * User can refresh CVC2 code of a token from WebSDK ## Hellgate iOS SDK (v1.0.0) The first release of the Hellgate® iOS SDK brings cardholder data tokenization to native iOS apps. Collect and tokenize the card number, CVC, and expiration date, and attach additional fields to capture cardholder information — so raw card data never touches your own backend. ## Hellgate Android SDK (v1.0.0) The first release of the Hellgate® Android SDK brings cardholder data tokenization to native Android apps. # Compose vs. Composed Source: https://developer.hellgate.io/platform/composition The Hellgate Cloud Platform offers individual composable services and as well as the ready to go composed version of it - Commerce. The Hellgate Cloud Platform follows two distinct usage patterns: **composing services yourself** or **using Commerce**, which is already composed for you. ## Composable Services The platform's individual services each do one thing well and are designed to slot into any payment stack. You pick the ones you need and compose them yourself. PCI-compliant tokenization vault. Stores card data, manages PCI tokens, network tokens, and generic tokens. The compliance layer underneath any payment stack. Real-time fraud intelligence. Scores every transaction as it passes through your payment flow. PSP abstraction layer. One integration, access to acquirers and payment service providers globally. Coming Soon Orchestration fabric. Routes payment flows between acquirers, PSPs, and fraud tools with intelligent routing and automatic retries. Each service is standalone and built for composability. ## Commerce: The Composed Product Commerce is a complete and opinionated payment orchestration service built by composing our platform's own services. It provides ready-made implementations of the capabilities most payment businesses need: * **Payments** — CIT, MIT, and the payment lifecycle management * **Authentications** — 3-D Secure and FIDO (Passkeys) * **Ecosystem enablement** — marketplace and partner network operating models Under the hood, Commerce routes through Hub to Guardian (tokenization), Link (PSP connectivity), and Specter (fraud intelligence) — the same services that will be made available to you as standalone components. Diagram showing Commerce routing through Hub to composed services: Specter for risk, Link for payment service providers, and Guardian for card schemes This means Commerce is not a walled garden. It is a production-proven composition of the same building blocks you have access to. If you ever want to build your own stack, the components are already there. ## Progressive Enhancement: BYOG Even when using Commerce, you can still exercise composability. By default, Hellgate manages a Guardian instance for sensitive data stored in your Commerce account. If you already operate a Guardian instance yourself, you can connect it instead. This is called **Bring Your Own Guardian (BYOG)**. Commerce continues to handle payments, authentications, and ecosystem operations — the Guardian component is simply replaced with yours. BYOG lets you: * **Reuse existing tokens** — tokens are accessible in both services * **Control the token store** — card data stays in the Guardian instance you own * **Bridge both patterns** — operate Guardian standalone and Commerce against the same underlying data BYOG is configured at the Commerce account level. Contact our support to set it up. ## Choosing Your Approach Choose individual services when you want to build custom payment orchestration and need specific components — a compliance layer, a fraud engine, a PSP connector — without adopting a pre-built product. Start with **Guardian** for PCI tokenization, the most commonly needed component. Additional services are releasing progressively. Choose **Commerce** when you want a production-ready payment orchestration service — payments, authentications, ecosystem support — without building each layer yourself. **Commerce** is a faster path to a complete payment stack. It is built from the same platform services, so you are not locked in. # The CPA Source: https://developer.hellgate.io/platform/cpa Introduction to the Composable Payment Architecture (CPA) The **Composable Payment Architecture (CPA)** is a term we coined recently. It describes how we built our payment systems and how we think you should build yours too. Instead of locking everything into one big system, CPA breaks things into smaller pieces you can mix and match however you need. ## What is CPA? CPA isn't a product or service, it's a way of thinking about payment systems. The idea is simple: build systems from independent pieces that work together, rather than one big monolithic thing. You compose services together instead of bundling everything into a single component. Each piece does one thing well, and you can swap them out or rearrange them as your needs change. ### How CPA Differs from Walled Gardens Most payment platforms are walled gardens. You get their way of doing things, bundled services you might not need, and switching costs that keep you locked in. CPA flips this around. You pick the services you actually need. You can mix services from different providers. You control your payment flows instead of adapting to someone else's constraints. When you need to change something, you swap out a component instead of rebuilding everything. ## Why CPA Matters Payment systems are messy. Everyone builds their own orchestration layers, optimized for their specific needs. Integration is painful. Scaling is hard. Changing anything requires rebuilding large parts of the system. Walled garden platforms make this worse. They lock you into their way of doing things. You adapt to them, not the other way around. CPA fixes this by letting you build from independent pieces. Change one component without touching the rest. Swap providers when you need to. Keep control of your architecture instead of being forced into someone else's mold. ## Core Principles CPA follows five simple principles: ### Separation of Concerns Keep different domains separate. Risk, settlement, data—each has its own boundaries. Change one without breaking the others. ### Composability Everything is replaceable. Swap components, rearrange them, extend them. No need to rebuild the whole system. ### Openness Use open APIs and contracts, not proprietary formats. Mix services from different providers. ### Programmability Everything is API-driven. Automate what you can. Extend what you need. ### Evolvability Change is constant. CPA lets you evolve incrementally—add a new PSP, update compliance rules, adopt new tech—without big rewrites. ## Composition Over Aggregation CPA prefers composition over aggregation. **Composition** means combining independent services. Each service stays separate—you can swap them out or rearrange them. **Aggregation** bundles multiple functions into one component. It seems easier at first, but you end up with tight coupling, less flexibility, and harder changes. ### Example: Fraud Checks in Authorization Flows Say you have a payment gateway that bundles fraud checks into authorization. Every authorization triggers a fraud check. To change this, you need to add flags and modify the authorization logic. Now authorization and fraud are tightly coupled—hard to test, hard to change, hard to swap providers. With CPA, authorization and fraud are separate services. You compose them however you need: * `fraud >=> auth` → check fraud first, then authorize * `auth >=> fraud` → authorize first, then check fraud Each service stays independent. You control how they work together. ## How We Build Hellgate with CPA CPA isn't just theory—it's how we build the Hellgate Cloud Platform. Every service follows CPA principles. They work together, but you can use them independently. We learned this from building payment systems and consulting in the payments world. We saw the problems: walled gardens, legacy systems, fragile protocols, systems that don't connect well. CPA is our answer. ### Our Platform Journey We're building Hellgate Cloud Platform as a collection of composable services. Commerce V2 is our CPA-based implementation—it's built entirely using CPA principles, composing independent services together to create flexible payment orchestration. One of those composable services is **Guardian**—our PCI-compliant tokenization service. Guardian is used within Commerce V2, but it's also available as a standalone service you can use independently or compose with your own services. More services are rolling out soon for general access: **Hub** and **Pulse**. Together with Guardian, Link, Specter, and Commerce V2, these make up our complete product stack. Each one is: * **Standalone** — Works on its own * **Composable** — Works with other services, ours or third-party * **CPA-aligned** — Built with composable architecture from the start Take what you need, when you need it. No forced bundles, no monolithic platform. ## Building with CPA When building payment software with CPA: 1. **Think in flows** — Break processes into steps you can rearrange 2. **Design for swapping** — Make components replaceable 3. **Focus on interfaces** — Define how components talk to each other, not how they work inside 4. **Plan for change** — Structure things so you can evolve incrementally 5. **Compose, don't bundle** — Keep services independent This approach makes systems easier to maintain, scale, and adapt as your needs change. ## Learn More See CPA in action: * [Commerce V2](/products/commerce/v2/overview) — A CPA-based implementation that orchestrates payment flows by composing services * [Guardian](/products/guardian/overview) — One of the composable services used in Commerce V2, also available standalone * [Link](/products/link/overview) — The protocol-abstraction runtime that decouples callers from providers, letting Commerce V2 and Specter reach PSPs and external risk engines * [Specter](/products/specter/overview) — The decisioning service for fraud detection and prevention, composable and extensible through Link CPA is our vision for payment architecture: systems that evolve, integrate, and adapt instead of locking you in. # Hellgate® Cloud Platform Source: https://developer.hellgate.io/platform/index Overview of the next generation payment platform Hellgate Cloud Platform Hellgate Cloud Platform The Hellgate Cloud Platform is a comprehensive payment infrastructure that provides secure, scalable, and flexible solutions for modern payment processing. It is the implementation of what we defined to be the **[Composable Payment Architecture](/platform/cpa)**. Built with composability and compliance at its core, the platform enables you to build sophisticated payment experiences while maintaining the highest security standards. ## Infrastructure & Deployment Hellgate Cloud Platform services are designed with enterprise-grade infrastructure in mind. ### Flexible Deployment Options Hellgate Cloud Platform services run on **dedicated, single-tenant infrastructure** hosted on Microsoft Azure across multiple regions. You choose a region close to your business workloads—reducing transaction latency, satisfying data residency requirements, and keeping your payment infrastructure where your operations are. For a full list of available locations, see the [Azure global infrastructure overview](https://azure.microsoft.com/en-us/explore/global-infrastructure/geographies/). **Always dedicated, always yours** Every Hellgate service is provisioned exclusively for your organisation. There are no noisy neighbours: compute, storage, and network resources are never shared with other clients. Your payment data is physically isolated at the infrastructure level, with no possibility of cross-tenant access. We operate and manage the infrastructure on your behalf, but full data ownership stays with you. **Commerce V2: shared infrastructure also available** Commerce V2 is the one exception to the dedicated-only model. Alongside dedicated deployments, we also operate a **shared Commerce V2 service**—available for both Test and Production tiers. This means you can run production payment workloads on our shared Commerce V2 infrastructure, not just test environments. The shared service is fully managed, fast to onboard, and a cost-effective starting point when complete infrastructure isolation is not a hard requirement. | | Shared | Dedicated | | - | - | - | | Tenancy | Multi-tenant | Single-tenant | | Data isolation | Logical | Physical | | Onboarding | Immediate | Requires provisioning | | Tiers | Test + Production | Test + Production | | Sizing | Shared resources | Exclusive reservations | If data sovereignty, regulatory compliance, or strict performance SLAs are a requirement for you, choose a dedicated Commerce V2 deployment. ## Platform Products The Hellgate Cloud Platform currently consists of two main products, with more services rolling out soon: A unified payment processing API for handling payments, tokens, authentications, and mobile wallets. Commerce V2 provides a programmable platform-as-a-service designed around four main domains: Payments, Tokens, Authentications, and Mobile Wallets. A fully PCI-compliant tokenization service delivered as managed dedicated infrastructure. Guardian provides a protective layer between your services and sensitive payment data, enabling composability without compliance concerns. Real-time fraud intelligence layer that analyzes every transaction as it moves through your payment flow, assigning risk scores and automatically blocking bad actors before they reach checkout. Unified PSP abstraction layer that gives you instant access to acquirers, PSPs, and services through a single interface, so you can scale globally without touching another integration. Coming Soon Orchestration fabric that connects your entire payment stack. Hub links acquirers, PSPs, and fraud prevention tools into one coordinated system with intelligent routing and automatic retries. Coming Soon Real-time observability and metrics layer that transforms raw transaction data into live insights, streaming events and surfacing performance trends across every flow. ## Why Hellgate Cloud Platform? The platform is designed to solve the common challenges in payment processing: * **Compliance by Design**: Built-in PCI DSS compliance ensures sensitive data never touches your systems * **[Composable Architecture](/platform/cpa)**: Products work together seamlessly or independently, giving you full control * **Unified APIs**: Consistent interfaces across all products reduce integration complexity * **Proximity Advantage**: Multi-region cloud infrastructure enables deployment close to your users for top-notch latency and performance * **Data Privacy**: Dedicated infrastructure ensures complete data isolation—your payment data stays yours, with no shared resources or cross-tenant access * **Enterprise Ready**: Dedicated infrastructure deployments with managed resources for your business * **Developer Friendly**: Comprehensive documentation, SDKs, and clear integration patterns # Overview Source: https://developer.hellgate.io/platform/network-tokens Network tokens replace card PANs with scheme-issued tokens for improved security and authorization rates. Supported across Guardian and Commerce. Network tokens are payment credentials issued directly by card schemes — Visa, Mastercard, American Express — as replacements for the actual card number (PAN). Instead of the raw PAN, transactions use a scheme-issued **Token PAN (TPAN)** scoped to a specific card and merchant context. Both [Guardian](/products/guardian/tokens/network-tokens) and [Commerce](/products/commerce/v2/tokens/network) support network tokens. Lifecycle management — including updates when cards expire or are re-issued — is handled automatically by the platform. Hellgate is a certified **Token Service Provider (TSP)** with the card schemes. As part of enabling network tokens, we register and onboard your business with the schemes on your behalf — so you receive scheme-issued tokens without holding your own token requestor certification or managing scheme enrollment yourself. **Benefits** * **Higher authorization rates** — scheme-recognized tokens are treated as lower-risk and authorize at higher rates than raw PANs * **Reduced fraud** — tokens are scoped to a specific merchant and cannot be reused outside that context * **Lower interchange fees** — some schemes offer preferential rates for network token transactions * **PSP portability** — a single network token can be used with multiple PSPs * **Fully managed onboarding** — as a certified TSP, Hellgate handles scheme registration and merchant onboarding for you ## Flows These flows show how a network token is created and used across a card's lifecycle, with Hellgate shown as a single actor. The exact API calls depend on the product — see [Guardian and Commerce](#guardian-and-commerce) for how each one provisions network tokens. ### Create a Network Token When you tokenize a card, Hellgate requests a network token from the card scheme. The scheme provisions a TPAN bound to the card and your merchant, which Hellgate associates with the token. ```mermaid theme={null} sequenceDiagram autonumber participant M as Merchant participant H as Hellgate participant S as Card Scheme M->>+H: Tokenize card H->>+S: Provision network token S->>-H: Token PAN (TPAN) H->>-M: Token (enriched with network token) ``` ### Store Credentials at Checkout When a merchant wants to store a card on file, the first authorization can run on the raw card number (FPAN) and the network token is provisioned only after that authorization succeeds. Provisioning a network token calls out to the card scheme and can add noticeable latency, so it should not be wired directly into the checkout path — authorize on the FPAN first to keep checkout fast, then provision the network token out of band once the payment is approved. ```mermaid theme={null} sequenceDiagram autonumber participant M as Merchant participant H as Hellgate participant P as PSP participant S as Card Scheme M->>+H: Tokenize card H->>-M: Token (FPAN) M->>+P: Authorize first payment with FPAN P->>-M: Authorization approved Note over M,S: Provision off the checkout path M->>+H: Provision network token from the stored card H->>+S: Provision network token S->>-H: Token PAN (TPAN) H->>-M: Network token ready for future payments ``` ### Customer Initiated Transaction (CIT) When a cardholder is present at checkout, a cryptogram must be requested before authorization. The cryptogram is a one-time proof of token ownership that your PSP uses to validate the transaction. ```mermaid theme={null} sequenceDiagram autonumber participant C as Customer participant M as Merchant participant H as Hellgate participant S as Card Scheme participant P as PSP C->>M: Initiate payment M->>+H: Request payment data H->>+S: Generate cryptogram S->>-H: Cryptogram H->>-M: TPAN + Cryptogram M->>+P: Authorize with TPAN + Cryptogram P->>-M: Authorization result M->>C: Payment confirmed ``` ### Merchant Initiated Transaction (MIT) For recurring charges where the cardholder is not present — subscriptions, installments, deferred billing — the scheme manages the TPAN lifecycle automatically. Most processors do not require a new cryptogram for MIT, simplifying the authorization flow. ```mermaid theme={null} sequenceDiagram autonumber participant M as Merchant participant H as Hellgate participant P as PSP M->>+H: Retrieve token H->>-M: Current TPAN M->>+P: Authorize with TPAN P->>-M: Authorization result ``` MIT cryptogram requirements vary by processor. Check your PSP's documentation for their specific requirements for recurring network token transactions. ## Supported Schemes * Visa * Mastercard * American Express * Discover *(coming soon)* * Diners Club *(coming soon)* ## Guardian and Commerce Both products support network tokens, but handle the relationship to PCI tokens differently: | | Guardian | Commerce | | - | - | - | | Provisioning | Explicit — provisioned separately from PCI tokens | Automatic — provisioned when you create a Commerce token (if enabled for your account) | | Token lifecycle | Independent — PCI token and network token lifecycles are separate | Unified — network token lifecycle is aligned with the Commerce token | Provisioning sources, cryptogram types, and API usage. Automatic provisioning, cryptogram request workflow, and PSP integration. ## Get Started Enable network tokens for testing and production. Add-on feature — requires activation. Test provisioning and cryptogram generation with the built-in Network Token Sandbox. # Setup Source: https://developer.hellgate.io/platform/network-tokens-setup Network tokens are an add-on feature on all Hellgate products. Learn how to enable them for testing and production. Network tokens are an add-on feature available on both Guardian and Commerce. They are not enabled by default and require activation through Hellgate support. ## Testing On the Test tier, Hellgate provides a built-in **Network Token Sandbox** that simulates card scheme responses without forwarding requests to real networks. Once network tokens are activated for your test account, the sandbox is available immediately — no additional configuration required. Learn about standard and deterministic testing modes, and how to test the full network token lifecycle. ## Production Activation To enable network tokens in production, contact your account representative or reach out to Hellgate support. Provide the following information: ### Business Details | Field | Notes | | - | - | | Legal Name | Must match official registration records | | Company Name (trade) | Trading or brand name if different from legal name | | Address | City and country of registered business | | Website | Primary domain | | Primary Contact | First name, last name, and email address | | VAT Code | Tax registration number, if applicable | | MCC | Four-digit Merchant Category Code classifying your primary business activity | ### Acquirer Details If you do not have a direct acquirer relationship, these values can be obtained from your PSP. | Field | Notes | | - | - | | `acquirerIca` | Acquirer Interbank Card Association (ICA) number | | `acquirerMerchantId` | Merchant ID assigned by your acquirer | | `acquirerBIN` | Acquirer Bank Identification Number | ### Optional Fields | Field | Notes | | - | - | | `programName` | Cardholder-facing merchant name (e.g. `"myShoppay"`). Required for Secure Card on File (SECURE\_COF) programs. | ## What Happens Next After receiving your details, Hellgate configures your account and confirms once network tokens are active. No code changes are required — token provisioning and lifecycle management work automatically once the feature is enabled. # Android SDK Source: https://developer.hellgate.io/platform/resources/sdks/android-sdk The Android SDK enables you to capture credit card data, additional personal data, and process them to create a Hellgate® Token. This token can then be used to process payments with the Hellgate® API. The SDK is written in Kotlin and is available as an Android dependency. It aims to provide a set of UI components and helpers that can be used to collect payment method data from the user with ease. ## Installation The SDK is available on [Maven Central](https://central.sonatype.com/artifact/io.hellgate/android-sdk). To install the SDK, add the following lines to your `build.gradle.kts` or respective `settings.gradle.kts` file. ```kotlin theme={null} // settings.gradle.kts dependencyResolutionManagement { repositories { mavenCentral() } } ``` Then add the following to your `build.gradle.kts` file: ```kotlin theme={null} dependencies { implementation("io.hellgate:android-sdk:") } ``` ## Usage ### Initialization of the UI Components The SDK provides a set of UI components that can be used to collect payment method data from the user. These could look similar to the following image, depending on the design of your application. Input form The form in the picture is composed of the following separate fields: * `CardNumberField` - A class that collects card details from the user. * `ExpiryDateField` - A class that collects the expiry date of the card from the user. * `CvcNumberField` - A class that collects the CVC of the card from the user. These classes are intended to be set up in a viewModel while the `ComposeUI` function of the class should be called in your compose UI code to actually draw the fields as part of your user interface. With the `onValueChange` callback, you can listen to changes in the field state and update your viewModel accordingly. ```kotlin theme={null} @Composable fun ComposeUI( onValueChange: (FieldState) -> Unit, modifier: Modifier = Modifier, onFocused: () -> Unit = {}, onBlur: () -> Unit = {}, colors: TextFieldColors = TextFieldDefaults.colors(errorTextColor = MaterialTheme.colorScheme.error), shape: Shape = OutlinedTextFieldDefaults.shape ) ``` #### FieldState The field state is a data class that holds the state of the field. It contains the following properties and will be updated as the user interacts with the field: ```kotlin theme={null} data class FieldState( val valid: Boolean = false, val empty: Boolean = true, val error: List = listOf(FieldError(FieldError.ErrorType.BLANK)) ) data class FieldError( val errorType: ErrorType, ) { enum class ErrorType { INVALID, INCOMPLETE, BLANK } } ``` #### Styling The `ComposeUI` function allows you to pass in an `androidx.compose.material3.TextFieldColors` object to style the field and overwrite default colors. If not overridden, the default colors will be used, with the `MaterialTheme.colorScheme.error` color being used for the error state. The `shape` parameter allows you to pass in a `androidx.compose.ui.graphics.Shape` object to style the field and overwrite the default shape. If not overridden, the `OutlinedTextFieldDefautls.shape` will be used. ### CardNumberField The `CardNumberField` class is used to collect the card number from the user. It can be initialized with a label that will be displayed on/above the input field. The following pictures give examples of how the field could look empty / filled and with a Luhn-invalid number. Empty card field Filled card field Error card field #### Code sample ```kotlin theme={null} class MyViewModel : ViewModel() { val cardNumberField = CardNumberField("Custom Card Number Label") val cardNumberState = MutableStateFlow(FieldState()) // ... } class MyScreen : ComponentActivity() { val viewModel by viewModels() override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContent { Column { CardNumberField() } } } @Composable fun CardNumberField() { viewModel.cardNumberField.ComposeUI( onValueChange = { viewModel.cardNumberState.value = it }, modifier = Modifier.fillMaxWidth(), onFocused = { /* handle focus */ }, onBlur = { /* handle blur */ } // shape = RoundedCornerShape(10.dp) // colors = TextFieldDefaults.colors(errorTextColor = Color.Red) ) } } ``` ### ExpiryDateField The `ExpiryDateField` class is used to collect the expiry date of the card from the user. The following pictures give examples of how the field could look empty / filled and with an invalid date. Empty expiry field Filled expiry field Error expiry field #### Code sample ```kotlin theme={null} class MyViewModel : ViewModel() { val expiryDataField = ExpiryDateField("Custom Expiry Label") val expiryDateState = MutableStateFlow(FieldState()) // ... } class MyScreen : ComponentActivity() { val viewModel by viewModels() override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContent { Column { ExpiryDateField() } } } @Composable fun ExpiryDateField() { viewModel.expiryDataField.ComposeUI( onValueChange = { viewModel.expiryDateState.value = it }, modifier = Modifier.fillMaxWidth(), onFocused = { /* handle focus */ }, onBlur = { /* handle blur */ } ) } } ``` ### CvcNumberField The `CvcNumberField` class is used to collect the CVC of the card from the user. The following pictures give examples of how the field could look empty / filled and with an invalid CVC. Empty CVC Filled CVC Error CVC #### Code sample In case you would like to use the `CvcNumberField` to also accept CVV (4-digit codes), you will need to pass in the maximum length of the CVC/CVV as a flow according to the card brand you are expecting. This flow can be retrieved from the `CardNumberField` class, which will try to imply the brand of the card based on the card number. If no flow is provided, the default maximum length of 3 digits will be used. ```kotlin theme={null} class MyViewModel : ViewModel() { val cardNumberField = CardNumberField("Custom Card Number Label") val cvcField = CvcNumberField(cardNumberField.maxBrandCvcLength) val cvcState = MutableStateFlow(FieldState()) // ... } class MyScreen : ComponentActivity() { val viewModel by viewModels() override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContent { Column { CardNumberField() CvcNumberField() } } } @Composable fun CvcNumberField() { viewModel.cvcField.ComposeUI( onValueChange = { viewModel.cvcState.value = it }, modifier = Modifier.fillMaxWidth(), onFocused = { /* handle focus */ }, onBlur = { /* handle blur */ } ) } } ``` ### Additional Fields The SDK also provides an AdditionalFields class that can be used to collect additional information from the user. The class is initialized with a type of the field and can be used to collect the following types of data: * `CARDHOLDER_NAME` - to collect the cardholder name from the user. In the future, the following fields will be added: * `EMAIL` - to collect the email from the user. * `BILLING_ADDRESS_LINE_1` - to collect the address details from the user. * `BILLING_ADDRESS_LINE_2` - to collect the address details from the user. * `BILLING_ADDRESS_LINE_3` - to collect the address details from the user. * `BILLING_ADDRESS_POSTAL_CODE` - to collect the address details from the user. * `BILLING_ADDRESS_CITY` - to collect the address details from the user. #### Code sample ```kotlin theme={null} class MyViewModel : ViewModel() { val cardholderNameField = DataField(AdditionalDataTypes.CARDHOLDER_NAME) val cardholderError = MutableStateFlow(false) val cardholderNameState = MutableStateFlow(AdditionalDataFieldState()) // ... } ``` ### Tokenization Once the user has filled in the fields, the data can be tokenized using the `tokenizeCard()` function of the `CardHandler` interface. To create an instance of the `CardHandler` interface, you can use the `cardHandler()` function of the `Hellgate` SDK object. This section is followed by the SDK-interface for reference. Please make sure to provide the base URL of the Hellgate® API as a parameter according to your Hellgate® usage scenario. As a second parameter, you will need to provide the `session_id` that you received from the Hellgate® API when you initialized the session on the server side. Once created, you can initialize a Hellgate® Object which helps you to handle a session. #### SDK Interface ```kotlin theme={null} fun initHellgate(hgBaseUrl: String = HELLGATE_URL, sessionId: String): Hellgate interface Hellgate { @MainThread suspend fun fetchSessionStatus(): SessionState @MainThread suspend fun cardHandler(): Result } enum class SessionState { REQUIRE_TOKENIZATION, COMPLETE, WAITING, UNKNOWN } ``` #### Code sample ```kotlin theme={null} class MyViewModel : ViewModel() { val sessionState = MutableStateFlow(null) fun createNewSession() { viewModelScope.launch { val sessionId: String = yourShopBackendClient().createSession().sessionId.orEmpty() sessionState.value = SessionState.UNKNOWN hellgate = initHellgate(HELLGATE_BASE_URL, sessionId) } } fun fetchSessionStatus() { viewModelScope.launch { val sessionStatus = hellgate.fetchSessionStatus() sessionState.value = sessionStatus } } } ``` After acquiring a Hellgate® object, you can fetch the session status and create a card handler object to tokenize the card data. Fetching the session status will return a `SessionState` object which can be used to determine the state of the session. If the session is in the state `REQUIRE_TOKENIZATION` you can proceed with tokenizing the card data. After the card data was handed in successfully, the session state will change to `COMPLETE`. To hand in card data please create a card handler object by calling the `cardHandler()` function of the `Hellgate` object. Now the`tokenizeCard()` function can be used to tokenize the card data by handing over the classes of the card number, expiry date and CVC number fields. Also in case you collected additional data, you can hand over a list of `DataField` objects to the function. The function will return a `TokenizeCardResponse` object. In case the tokenization was successful, the response will be of type `TokenizeCardResponse.Success` and contain the ID of the token. In case the tokenization failed, the response will be of type `TokenizeCardResponse.Failure` and contain a message and an optional throwable. #### SDK Interface ```kotlin theme={null} interface CardHandler { suspend fun tokenizeCard( cardNumberField: CardNumberField, cvcNumberField: CvcNumberField, expiryDateField: ExpiryDateField, additionalData: List = emptyList(), ): TokenizeCardResponse } sealed class TokenizeCardResponse { data class Success(val id: String) : TokenizeCardResponse() data class Failure( val message: String, val validationErrors: List = emptyList(), ) : TokenizeCardResponse() } // Possible validation errors sealed interface CardDataValidationError { val message: String } data object InvalidCardNumber : CardDataValidationError { override val message: String = "Invalid card number" } data object InvalidExpiryDate : CardDataValidationError { override val message: String = "Invalid expiry date" } data object InvalidCvc : CardDataValidationError { override val message: String = "Invalid cvc" } ``` #### Code sample ```kotlin theme={null} fun submit() { viewModelScope.launch { hellgate.cardHandler().fold( onSuccess = { debugLog("$TAG Success, cardHandler created") val response = it.tokenizeCard( cardNumberField, cvcField, expiryDateField, if (cardholderNameState.value.empty) emptyList() else listOf(cardholderNameField), ) handleResponse(response) textValue = response.toString() }, onFailure = { debugLog("$TAG Failure, cardHandler not created: ${it.message}") }, ) } } private fun handleResponse(response: TokenizeCardResponse) { when (response) { is TokenizeCardResponse.Success -> { debugLog("$TAG Tokenization successful, token ID: ${response.id}") textValue = "Token ID: ${response.id}" sessionState.value = SessionState.COMPLETED } is TokenizeCardResponse.Failure -> { debugLog("$TAG Tokenization failed: ${response.message}, Validation Errors: ${response.validationErrors}") textValue = "Error: ${response.message}" sessionState.value = SessionState.FAILURE } } } ``` # iOS SDK Source: https://developer.hellgate.io/platform/resources/sdks/ios-sdk The iOS SDK enables you to capture credit card data, additional personal data, and process them to create a Hellgate® Token. This token can then be used to process payments with the Hellgate® API. The SDK is written in Swift and is available as an iOS dependency. It aims to provide a set of UI components and helpers that can be used to collect payment method data from the user with ease. ## Installation[​](#installation "Direct link to Installation") Installation can be done in a number of ways. Pick the distribution method that best suits your project: ### CocoaPods[​](#cocoapods "Direct link to CocoaPods") The SDK is available on [CocoaPods](https://cocoapods.org/pods/Hellgate-iOS-SDK). Add the following line to your `Podfile`: ```ruby theme={null} pod 'Hellgate-iOS-SDK' ``` ### Carthage[​](#carthage "Direct link to Carthage") 1. Add the following line to your `Cartfile`: ```ruby theme={null} github "starfish-codes/hgate2-ios-headless-sdk" ``` 2. Run in same directory as your `Cartfile`: ```bash theme={null} carthage update --use-xcframeworks ``` 3. Add the built XCFrameworks to your project under "Frameworks and Libraries" ### Swift Package Manager[​](#swift-package-manager "Direct link to Swift Package Manager") The SDK is available on [Swift Package Index](https://swiftpackageindex.com/starfish-codes/hgate2-ios-headless-sdk). Add the package via Xcode from `https://github.com/starfish-codes/hgate2-ios-headless-sdk.git` ## Usage[​](#usage "Direct link to Usage") The SDK provides a set of UI components that can be used to collect payment method data from the user. The components are composed of the following separate fields: * `CardNumberView` - A View that collects card details from the user. * `ExpiryDateView` - A View that collects the expiry date of the card from the user. * `CvcView`- A View that collects the CVC of the card from the user. First you'll need to import the project: ```swift theme={null} import Hellgate_iOS_SDK ``` ### User Interface[​](#user-interface "Direct link to User Interface") #### Initialization of the UI Components[​](#initialization-of-the-ui-components "Direct link to Initialization of the UI Components") These components are intended to be used in a SwiftUI project. The validity of the users entered data can be listened in on by creating a `@State` property on your parent view. Form #### ViewState[​](#viewstate "Direct link to ViewState") Each view below requires a binding to a `ViewState` property. The `ViewState` of each component is updated as soon as their related component changes. ##### SDK Interface[​](#sdk-interface "Direct link to SDK Interface") ```swift theme={null} public enum ComponentState: String { case complete case incomplete case blank case invalid } public struct ViewState { public let state: ComponentState } ``` ##### Code Sample[​](#code-sample "Direct link to Code Sample") ```swift theme={null} struct ContentView: View { @State var cardNumberViewState = ViewState(state: .blank) @State var expiryViewState = ViewState(state: .blank) @State var cvcViewState = ViewState(state: .blank) // ... } ``` #### CardNumberView[​](#cardnumberview "Direct link to CardNumberView") The `CardNumberView` view is used to collect the card number from the user. It can be initialized with the card brand image either on the leading or trailing side of the input or hidden. CardNumberView ##### Code Sample[​](#code-sample-1 "Direct link to Code Sample") ```swift theme={null} CardNumberView(viewState: $cardNumberViewState, image: .leading) .border() struct ContentView: View { @State var cardNumberViewState = ViewState(state: .blank) var body: some View { CardNumberView(viewState: $cardNumberViewState, image: .leading) .border() } } ``` #### ExpiryDateView[​](#expirydateview "Direct link to ExpiryDateView") The `ExpiryDateView` view is used to collect the expiry date of the card from the user. ExpiryDateView ##### Code Sample[​](#code-sample-2 "Direct link to Code Sample") ```swift theme={null} struct ContentView: View { @State var expiryViewState = ViewState(state: .blank) var body: some View { ExpiryDateView(viewState: $expiryViewState) .border() } } ``` #### CvcView[​](#cvcview "Direct link to CvcView") The `CvcView` view is used to collect the CVC or CVV number of the card from the user. ##### Code Sample[​](#code-sample-3 "Direct link to Code Sample") ```swift theme={null} struct ContentView: View { @State var cvcViewState = ViewState(state: .blank) var body: some View { CvcView(viewState: $cvcViewState, length: .cvc) .border() } } ``` #### Additional Fields[​](#additional-fields "Direct link to Additional Fields") The `AdditionalFieldsView` is used to capture additional information about the card holder. The view is initialized with a type of the field and can be used to collect the following types of data: * `CARDHOLDER_NAME` - to collect the cardholder name from the user. In the future, the following fields will be added: * `EMAIL` - to collect the email from the user. * `BILLING_ADDRESS_LINE_1` - to collect the address details from the user. * `BILLING_ADDRESS_LINE_2` - to collect the address details from the user. * `BILLING_ADDRESS_LINE_3` - to collect the address details from the user. * `BILLING_ADDRESS_POSTAL_CODE` - to collect the address details from the user. * `BILLING_ADDRESS_CITY` - to collect the address details from the user. ##### Code Sample[​](#code-sample-4 "Direct link to Code Sample") ```swift theme={null} struct ContentView: View { var body: some View { AdditionalFieldsView(type: .CARDHOLDER_NAME) .border() } } ``` ### Tokenization[​](#tokenization "Direct link to Tokenization") Once the user has filled in the fields, the data can be tokenized using the `tokenizeCard()` function of the `CardHandler` interface. To create an instance of the `CardHandler` interface, you can use the `cardHandler()` function of the Hellgate® SDK object. This section is followed by the SDK-interface for reference. Please make sure to provide the base URL of the Hellgate® API as a parameter according to your Hellgate® usage scenario. As a second parameter, you will need to provide the `session_id` that you received from the Hellgate® API when you initialized the session on the server side. See the section Tokenize on the Web for more information. Once created, you can initialize a Hellgate® Object which helps you to handle a session. ##### SDK Interface[​](#sdk-interface-1 "Direct link to SDK Interface") ```swift theme={null} func initHellgate(baseUrl: URL, sessionId: String) async -> Hellgate protocol Hellgate { func fetchSessionStatus() async -> SessionState func cardHandler() async -> Result } enum SessionState: String { case REQUIRE_TOKENIZATION case WAITING case COMPLETED case UNKNOWN } ``` ##### Code Sample[​](#code-sample-5 "Direct link to Code Sample") ```swift theme={null} import Hellgate_iOS_SDK class ContentViewViewModel: ObservableObject { private var hellgate: Hellgate? @Published var secretKey = "" @Published var sessionId = "" @Published var sessionState: SessionState? @MainActor func initSession() async { self.sessionId = await fetchSessionId() self.hellgate = await initHellgate(baseUrl: HELLGATE_URL, sessionId: sessionId) } @MainActor func sessionStatus() async { guard let hellgate = self.hellgate else { return } self.sessionState = await hellgate.fetchSessionStatus() } } ``` After acquiring a Hellgate® object, you can fetch the session status and create a card handler object to tokenize the card data. Fetching the session status will return a SessionState enum which can be used to determine the state of the session. If the session is in the state `REQUIRE_TOKENIZATION` you can proceed with tokenizing the card data. After the card data was handed in successfully, the session state will change to `COMPLETE`. To hand in card data please create a card handler object by calling the `cardHandler()` function of the Hellgate® object. Now the `tokenizeCard()` function can be used to tokenize the card data by handing over the classes of the card number, expiry date and CVC number fields. Also, in case you collected additional data, you can hand over a list of DataField objects to the function. The function will return a TokenizeCardResponse object. In case the tokenization was successful, the response will be of type `TokenizeCardResponse.Success` and contain the ID of the token. In case the tokenization failed, the response will be of type `TokenizeCardResponse.Failure` and contain a message. ##### SDK Interface[​](#sdk-interface-2 "Direct link to SDK Interface") ```swift theme={null} protocol CardHandler { func tokenizeCard( _ cardNumberView: ViewState, _ cvcView: ViewState, _ expiryView: ViewState, _ additional: [AdditionalFieldType: ViewState] ) async -> Result } enum TokenizeCardResponse { public struct Success: Decodable { public let id: String } public struct Failure: Error, Decodable { let message: String var localizedDescription: String { message } } } ``` ##### Code Sample[​](#code-sample-6 "Direct link to Code Sample") ```swift theme={null} @MainActor func tokenize() async { guard let hellgate = self.hellgate else { return } let result = await hellgate.cardHandler() if case let .success(handler) = result { let response = await handler.tokenizeCard( cardNumberViewState, cvcViewState, expiryViewState, [:] ) switch response { case let .success(result): print("Token: \(result.id)") case let .failure(err): print(err.localizedDescription) } } } ``` # API Source: https://developer.hellgate.io/platform/resources/sdks/web-sdk/api Web SDK API reference for client sessions and payment processing. ## Create Client This method will create and initialise the Hellgate® client using an existing `session_id`. ```javascript theme={null} const { Hellgate } = window; const client = await Hellgate.init('7f2c9460-7e2e-403f-a08a-bc58cdcaea83', { base_url: 'https://sandbox.hellgate.io', }); ``` | Parameter | Type | Required? | Description | | - | - | - | - | | sessionID | String | Yes | The session id that was handed over to the integrator as part of an action requirement. | | options | Object | No | [Options](#element-options) to customise the form to specific requirements | ### Options | Attribute | Type | Description | | - | - | - | | base\_url | String | Set custom Hellgate® backend. Default -`https://api.hellgate.io` | | challenge\_3ds\_container | HTMLElement | Custom DOM container for rendering 3D Secure Challenge iframe. Default - `undefined` | | challenge\_3ds\_label\_text | String | Display custom label text for 3D Secure Challenge iframe | ### Session Complete This event is called once the session is completely finished. ```javascript theme={null} client.onSessionComplete((result) => { // handling logic }); ``` The handler receives a `result` object. #### Session Result | Attribute | Type | Description | | - | - | - | | status | "success" \| "failure" \| "pending" | Status of the current processing | | data | Object | Result data | #### Session Result status | Name | Description | | - | - | | success | The SDK session was completed successfully. Result could be found in the `data` attribute | | failure | The SDK session failed. Failure reason and reason code could be found in the `data` attribute | | pending | The SDK session concluded successfully; however, the ultimate outcome of the operation initiated by the session remains indeterminate due to its asynchronous nature. The definitive results will be delivered to the backend via webhook or can be retrieved through an API request. | ## Create Card Handler In order to process card payments (and later other payment methods as well), a handler has to be obtained from the client. ```javascript theme={null} const cardHandler = await client.use('CARD'); ``` ## Reset Card Handler This method will remove the card form or card fields from the client, disconnect handlers and unmount the fields from the DOM. ```javascript theme={null} cardHandler.reset(); ``` ## Handling Cards ### Create Card Form This method will create a new one-line card form. ```javascript theme={null} const cardForm = cardHandler.createForm(options); ``` #### Parameters | Parameter | Type | Required? | Description | | - | - | - | - | | options | Object | No | [Options](#element-options) to customise the form to specific requirements. | ### Create Card Fields This method will create the full set of card fields as shown below in the demo. ```javascript theme={null} const { cardNumber, expiryDate, securityCode } = cardHandler.createFormFields(options); ``` #### Parameters | Parameter | Type | Required? | Description | | - | - | - | - | | options | Object | No | [Options](#element-options) to customise the form fields to specific requirements. The options can be prefixed with `cardNumber: {}`, `expiryDate: {}`, and `securityCode: {}` to apply them specifically (e.g. `cardNumber: {placeholder: 'Custom Placeholder'}`). | | options.expiryDateElement | String | `"combined" \| "separate"` | `combined` - expiration month and year is a single form field. `separate` - expiration month and year are two separate fields. Default: `combined`. | ### Create Text Field The "text" element type is a secure replacement for the `` tag and enables collecting user string data like Cardholder Name as demonstrated. ```javascript theme={null} cardHandler.createTextField({ placeholder: 'Cardholder Name' }); ``` The created `TextField` can be directly passed as a property for `additionalData` and the value does not need to be extracted. #### Parameters | Parameter | Type | Required? | Description | | - | - | - | - | | options | Object | No | [Options](#element-options) to customise the form fields to specific requirements. | ### 3D-Secure 3D Secure handling works out of the box with the SDK and usually doesn't require any extra code. ### Process This method will read the data from the card form or the card fields and send them to processing. Prerequisite is that card fields or a card form was created before. A loading indicator can be shown until the promise is resolved. ```javascript theme={null} const result = await cardHandler.process({ // cardholderTextField can be created by cardHandler.createTextField() // the value from the text field will be extracted automatically during processing cardholder_name: cardholderTextField, }); ``` We recommend performing a preliminary check to ensure that the card [data](#formstatechange) is complete and valid before calling this method. Otherwise, the promise returned by the `cardHandler.process()` call will be rejected with an [Error](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Error) object. #### Parameters | Parameter | Type | Required? | Description | | - | - | - | - | | additionalData | Object | No | Further information that needs to be passed into the processing. | #### Additional Data | Attribute | Type | Description | | - | - | - | | cardholder\_name | String \| TextField | The name of the cardHolder | | email | String \| TextField | Email address of the customer | | billing\_address.line1 | String \| TextField | The first line of the address | | billing\_address.line2 | String \| TextField | The second line of the address | | billing\_address.line3 | String \| TextField | The third line of the address | | billing\_address.postal\_code | String \| TextField | The postal code of the address | | billing\_address.city | String \| TextField | The name of the city | | billing\_address.country | String \| TextField | The three letter country code following [ISO3166-Alpha3](https://en.wikipedia.org/wiki/ISO_3166-1_alpha-3) | #### Token lifecycle Pass these fields in the same object as the additional data to control how long the resulting token is retained. Both `process()` and `tokenize()` accept them. | Attribute | Type | Description | | - | - | - | | ephemeral | Boolean | When `true`, the token is short-lived: Guardian deletes it after a short TTL (15 minutes by default). When `false`, the token is persisted until it is deleted through the API — set it to `false` only on cardholder consent. | | expiration\_date | String | An ISO-8601 timestamp setting an explicit expiration for the token. It must lie in the future, at most 30 days ahead; otherwise the session completes with `status: "failure"` and `reason_code: "VALIDATION_ERROR"`. When supplied, it takes precedence and `ephemeral` is ignored. | When both fields are omitted, the token is persisted. An absent flag is treated as a legacy client that predates these fields, so the pre-lifecycle behaviour is kept for backward compatibility. Don't rely on this implicit default — pass `ephemeral` explicitly, and persist only after the cardholder consented. For a "Save my card" consent UX, wire the checkbox straight into the flag: ```javascript theme={null} const result = await cardHandler.process({ cardholder_name: cardholderTextField, // persist the token only when the cardholder ticked "Save my card" ephemeral: !saveMyCardCheckbox.checked, }); ``` #### Session lifecycle modes The backend assigns every tokenization session a lifecycle mode when the session is created. The SDK exposes it via `cardHandler.getLifecycle()`: | Mode | When | Effect on `process()` / `tokenize()` | | - | - | - | | `user_choice` | Default | Your `ephemeral` / `expiration_date` arguments are sent as provided. | | `ephemeral_only` | The session was created with an explicit TTL (`expires_in`) | The SDK always requests an ephemeral token; your lifecycle arguments are ignored and the session's TTL applies. | | `persistent_only` | Network-token sessions | The SDK always requests a persistent token; your lifecycle arguments are ignored. | Use the mode to adapt your UI — for example, only render the "Save my card" checkbox when the cardholder actually has a choice: ```javascript theme={null} const showSaveMyCard = cardHandler.getLifecycle() === 'user_choice'; ``` #### Result Session result [data](#session-result) #### Result status Session result [status](#session-result-status) # Elements API The Elements API allows to interact with the created card form or the card fields. ## Mount Element This operation mounts an element to the DOM at the specified element. ```javascript theme={null} await cardForm.mount('#my-card-form-id'); ``` ### Parameters | Parameter | Type | Required? | Description | | - | - | - | - | | selector | String | Yes | A CSS-selector that identifies the container in the DOM to where the element will be mounted | ## Update Element This operations allows to change the options on an element. Please note that not all options can be changed. ```javascript theme={null} cardNumberField.update({ icon: 'none' }); ``` ### Parameters | Parameter | Type | Required? | Description | | - | - | - | - | | options | Object | No | The [options](#element-options) to customise the element. | ## Unmount Element This operation removes an element from the DOM. ```javascript theme={null} cardNumberField.unmount(); ``` ## Element Values / States ### Focus Set the focus on an element's input. ```javascript theme={null} cardNumberField.focus(); ``` ### Clear Clear the value of an element. ```javascript theme={null} cardNumberField.clear(); ``` ### Blur Blur an element's input. ```javascript theme={null} cardNumberField.blur(); ``` ## Element Options | Attribute | Element | Type | Description | | - | - | - | - | | style | All | Object | [Data](#element-styles) that defines the styling of the element. | | height | All | String | [CSS property](https://developer.mozilla.org/en-US/docs/Web/CSS/height) to customise the height of the element | | disabled | All | Boolean | Set the disabled state of the form field(s). | | autocomplete | All | String | String used to set the [autocomplete attribute](https://developer.mozilla.org/en-US/docs/Web/HTML/Attributes/autocomplete) of the input(s). Expected values are: off (default), or on. | | placeholder | text, cardNumber, cardExpirationDate, cardVerificationCode | String | String to customise the placeholder attribute of the form field. | | ariaLabel | text, cardNumber, cardExpirationDate, cardVerificationCode | String | String to customise the aria-label attribute of the form field. | | icon | card, cardNumber | String | String to determine the position of the icon in the cardNumber field. `left` is the default value. It can be overwritten to `right` and to `none` in order to hide the icon entirely. | | schemes | card, cardNumber | String\[] | Whitelist of card schemes which are supported by the merchant. Available schemes: `visa`, `mastercard`, `american-express`, `discover`, `diners-club`, `jcb` `unionpay`. The input of a restricted scheme will lead to a validation error in the form. All schemes are supported by default. | ## Element Styles The form fields are encapsulated into iframes to allow for maximum protection of sensitive data. This means that CSS from the embedding page cannot be propagated directly into the form or the fields. ```javascript theme={null} cardNumberField.update({ style: { fonts: ['https://fonts.googleapis.com/css2?family=Playfair:wght@300&display=swap'], base: { color: '#fff', fontWeight: 500, fontFamily: '"Playfair"', fontSize: '16px', '::placeholder': { color: '#6b7294', }, ':disabled': { backgroundColor: '#ffff3b', }, }, invalid: { color: '#ffa474', }, }, }); ``` | Attribute | Required? | Description | | - | - | - | | fonts | No | List of URLs to fonts on the Google API. | | base | No | Basic styling of the element. The other variants will use it as baseline. | | complete | No | Variant of the baseline for an element with completed input. | | empty | No | Variant of the baseline for an element with empty input. | | invalid | No | Variant of the baseline for an element with invalid input. | You can customise the elements using these pseudo-classes and pseudo-elements inside each variant using a nested object: * `:hover` * `:focus` * `:disabled` * `::placeholder` * `::selection` These CSS properties are supported in terms of styling: * backgroundColor * color * fontFamily * fontSize * fontSmooth (will be mapped to user-agent naming automatically) * fontStyle * fontVariant * fontWeight * lineHeight * letterSpacing * textAlign * padding * textDecoration * textShadow * textTransform # Events Both the clients and the elements send out events so that solutions can actively react to changes in state. ## Card Handler Events ### FormStateChange This event notifies every time the card form fields state changes. ```javascript theme={null} cardHandler.onStateChange((state) => { // handling logic }); ``` The handler receives an aggregated `state` object that is derived from all card fields states. ```json theme={null} { "valid": false, "complete": false, "empty": true, "error": "cardNumber is not complete" } ``` | Attribute | Type | Description | | - | - | - | | valid | Boolean | Indicates that all fields content successfully passed validation. | | complete | Boolean | Indicates that all fields are valid and maskSatisfied properties are true | | empty | Boolean | Form will be empty only if all inputs are | | error | String | Human readable error message with an explanation why `valid: false` | | errorCode | String | Values are `NOT_COMPLETE`, `SCHEME_NOT_SUPPORTED`, `LUHN_CHECK_FAILED` and `INVALID_EXPIRY_DATE` | ## 3D-Secure Events In standard cases 3D Secure handling works out of the box with the SDK and usually doesn't require any extra code. However, there are some events that can be used to handle the 3DS Challenge experience in the UI. In order to use the 3DS Handler, it has to be obtained from the client: ```javascript theme={null} const threeDSHandler = await client.use('3DS'); ``` ### Decoupled Authentication Decoupled authentication can be supported by the SDK with a specific event. ```javascript theme={null} threeDSHandler.onDecoupledAuth((cardholderInfo) => { // handle decoupled flow here and display cardholderInfo on the UI // to inform the customer how they should complete 3DS authentication }); ``` | Parameter | Type | Description | | - | - | - | | cardholderInfo | String | Instructions from the card issuer what method the customer should use to complete 3DS authentication. For example, mobile banking app | ### Start Challenge This event is called once the 3DS Challenge is started. It can be used to show a loading indicator or to inform the customer about the ongoing process. ```javascript theme={null} threeDSHandler.onStartChallenge(() => { // called once 3DS Challenge is started }); ``` ### Challenge Finished This event is called once the 3DS Challenge is finished. It can be used to hide the loading indicator or to inform the customer about the result. ```javascript theme={null} threeDSHandler.onChallengeFinished(() => { // called when 3DS Handler finishes the task }); ``` ## Element Events ### Change This event notifies that the value of an element has changed. By default this event is emitted `onBlur` which can be overwritten with the element options. ``` cardNumberField.on('change', (changeEvent) => { // handling logic}); ``` The handler receives a ChangeEvent with information about the state of the element. ``` { "complete": true, "valid": false, "empty": false, "error": "Card Number Luhn check failed", "cardBrand": "amex"} ``` | Attribute | Element | Type | Description | | - | - | - | - | | valid | All | Boolean | Indicates that the content successfully passed validation. | | empty | All | Boolean | Indicates that the element is empty. | | error | All | String | Human readable error message with an explanation why `valid: false` | | cardBrand | CardNumber | String | Values are `amex`, `visa`, `mastercard`, `diners`, `jcb`, and `unionpay`. | ### Focus This event indicates that an element is focussed. ```javascript theme={null} cardNumberField.on('focus', () => { // handling logic }); ``` ### Blur This event indicates that an element lost focussed. ```javascript theme={null} cardNumberField.on('blur', () => { // handling logic }); ``` ### Key Down This event indicates that a key was pressed on the element. ```javascript theme={null} cardNumberField.on('keydown', (keyDownEvent) => { // handling logic }); ``` The handler receives a KeyDownEvent with information about the state of the element. ```json theme={null} { "key": "enter", "ctrlKey": false, "altKey": false, "shiftKey": false, "metaKey": false } ``` | Attribute | Type | Description | | - | - | - | | key | String | Indicator of the key that was pressed. Can be `Enter` or `Escape`. | | ctrlKey | Boolean | Indicates if the control key was pressed when the event occurred. | | altKey | Boolean | Indicates if the alt / option key was pressed when the event occurred. | | shiftKey | Boolean | Indicates if the shift key was pressed when the event occurred. | | metaKey | Boolean | Indicates if the meta (e.g. command on a Mac) key was pressed when the event occurred. | # Overview Source: https://developer.hellgate.io/platform/resources/sdks/web-sdk/overview Headless Web SDK for payment experiences on the web. In order to make use of the Web SDK you need to create a client-sharable, short-lived secret. This secret is called a `session` as it represents a session between your web application and the Hellgate®. Create a session for tokenization using the session API. ```bash title="Example: Create session" theme={null} curl --request POST \ --url https://api.hellgate.io/tokens/session \ --header 'content-type: application/json' ``` The response includes the `session_id` of the created session. ```json title="Example: Session response" theme={null} { "session_id": "7f2c9460-7e2e-403f-a08a-bc58cdcaea83" } ``` Want to confirm the session works before writing any browser code? Paste the `session_id` into the [WebSDK testing page](/platform/resources/testing/web-sdk) to run the full card flow against it. Send the `session_id` to your web application in the customer's browser. The `session_id` is safe to share with the client. You must not distribute your server-side API keys to the clients. Use the provided `session_id`, which is short-lived (with a default of 15 minutes) and has a very limited scope. This protects you from malicious acts. After the `session_id` is handed over to the client, initialise the headless SDK. ### Load the SDK The SDK is distributed via our CDN or from NPM. ```bash title="Example: Install via NPM" theme={null} npm install --save @starfish-codes/hellgate ``` ```html title="Example: Load via CDN" theme={null} ``` ### Initialise the SDK With the `session_id` and the loaded SDK, initialise a headless client for your browser session. ```javascript title="Example: Initialise client" theme={null} import { Hellgate } from '@starfish-codes/hellgate'; // Create the Hellgate client. Make sure you await the result of the // asynchronous initialisation, as subsequent actions will rely on it. const client = await Hellgate.init('7f2c9460-7e2e-403f-a08a-bc58cdcaea83', { base_url: 'https://sandbox.hellgate.io', }); ``` The headless client keeps a secure connection to the Hellgate® backend. Connect it with your application to proceed. Hellgate's Headless SDK enables secure card payment data entry, yet is fully embedded in your web application. The secure communication channel between the SDK and the Hellgate® allows the sensitive card data to be exchanged for so-called tokens. These are made available to you as a payment method. Use the `CARD` handler to tokenize cardholder data. ```javascript title="Example: Get card handler" theme={null} const cardHandler = await client.use('CARD'); ``` Based on the handler, two form types can be rendered in your front-end. ### One-Line Card Form A compact way to collect card details: separate entry fields rendered as one unit. ```javascript title="Example: One-line card form integration" theme={null} const { Hellgate } = window; const client = await Hellgate.init('d5529f03-e84a-4a43-867c-b3412a097e63', { base_url: 'https://sandbox.hellgate.io', }); const cardHandler = await client.use('CARD'); const cardForm = cardHandler.createForm({ style: { fonts: ['https://fonts.googleapis.com/css2?family=Nunito:wght@300;400;500;600;700;800&display=swap'], base: { fontFamily: 'Nunito, sans-serif', color: '#111', fontSize: '16px', '::placeholder': { color: '#6b7280' }, }, }, }); const cardholderField = cardHandler.createTextField(); cardForm.mount('#my-form-element'); cardholderField.mount('#cardholder-element'); const submitButton = document.getElementById('my-submit-button'); submitButton.addEventListener('click', async () => { try { const result = await cardHandler.process({ cardholder_name: cardholderField, }); const resultElement = document.getElementById('result'); const errorElement = document.getElementById('error'); if (result.status === 'success' || result.status === 'pending') { resultElement.textContent = 'Processing has been successfully completed'; errorElement.textContent = ''; } else { resultElement.textContent = ''; errorElement.textContent = result.data.reason; } } catch (err) { // handle error (e.g. network issue or backend error) } }); ``` ### Separated Card-Data Fields For more flexibility in layout, create individual elements and mount them separately. ```javascript title="Example: Separated card fields" theme={null} const { Hellgate } = window; const client = await Hellgate.init('d5529f03-e84a-4a43-867c-b3412a097e63', { base_url: 'https://sandbox.hellgate.io', }); const cardHandler = await client.use('CARD'); const { cardNumber, expiryDate, securityCode } = cardHandler.createFormFields({ style: { base: { color: '#111', fontSize: '14px', }, }, }); await Promise.all([ cardNumber.mount('#my-card-number-element'), expiryDate.mount('#my-expiry-date-element'), securityCode.mount('#my-security-code-element'), ]); const submitButton = document.getElementById('my-submit-button'); submitButton.addEventListener('click', async () => { try { const result = await cardHandler.process({ cardholder_name: 'Cardholder Name', }); const resultElement = document.getElementById('result'); const errorElement = document.getElementById('error'); if (result.status === 'success' || result.status === 'pending') { resultElement.textContent = 'Processing has been successfully completed'; errorElement.textContent = ''; } else { resultElement.textContent = ''; errorElement.textContent = result.data.reason; } } catch (err) { // handle error } }); ``` ### Separated expiration month and year Use `expiryDateElement: 'separate'` to get distinct month and year fields. ```javascript title="Example: Separate month and year fields" theme={null} const { cardNumber, expiryMonth, expiryYear, securityCode } = cardHandler.createFormFields({ style: { base: { color: '#111', fontSize: '14px', }, }, expiryDateElement: 'separate', }); await Promise.all([ cardNumber.mount('#my-card-number-element'), expiryMonth.mount('#my-expiry-month-element'), expiryYear.mount('#my-expiry-year-element'), securityCode.mount('#my-security-code-element'), ]); ``` # Network Token Source: https://developer.hellgate.io/platform/resources/testing/network-token Test network token provisioning and cryptogram generation with the Hellgate Cloud Network Token Sandbox Hellgate Cloud provides a built-in **Network Token Sandbox** for testing network tokens on Test tier deployments. The Network Token Sandbox serves as the default backend for network token provisioning and enables you to test network token functionality across both Commerce and Guardian products. ## Overview The Hellgate Cloud Network Token Sandbox supports two modes of network token testing: * **Standard network token testing**: Generates network tokens from PAN data and allows you to request cryptograms (payment data) for them * **Deterministic network tokens**: Uses the FPAN (Fundamental PAN) of the underlying card as the TPAN (Token PAN) for transitive testing scenarios Both modes work seamlessly on Commerce and Guardian, allowing you to test the complete network token lifecycle from provisioning through cryptogram generation and payment authorization. ## Standard Network Token Testing When you provision a network token using PAN data, the Network Token Sandbox: 1. Accepts the FPAN and associated card data 2. Generates a network token (TPAN - Token PAN) from the FPAN 3. Associates the network token with your tokenized card 4. Enables cryptogram requests for payment authorization This standard behavior allows you to test the complete network token lifecycle, from provisioning through cryptogram generation and payment authorization. ## Deterministic Network Tokens For advanced testing scenarios involving transitive testing with acquirers and processors, Hellgate Cloud provides deterministic network tokens. Deterministic TPANs are **not active by default**. Contact support to activate this feature for your account. ### How Deterministic Network Tokens Work Deterministic network tokens use the FPAN (Fundamental PAN) of the underlying card as the TPAN (Token PAN). This means: * The network token (TPAN) will be identical to the original card PAN (FPAN) * Cryptograms can be deterministically generated for the same TPAN * You can create predictable, reproducible testing scenarios ### When to Use Deterministic Network Tokens Deterministic network tokens are particularly useful for: * **Transitive testing**: Testing integrations with connected sandboxes (e.g., acquirer or PSP sandboxes) * **Predictable scenarios**: Creating reproducible test cases where the same PAN always generates the same network token * **Third-party sandbox integration**: Enabling access to sandbox behavior on connected third-party environments (e.g., acquirer or PSP test systems) ### Benefits With deterministic network tokens, you can: * Provision network tokens and deterministically generate the TPAN with a cryptogram * Test end-to-end flows through multiple systems (Hellgate Cloud → Acquirer → Processor) * Maintain consistency across test runs * Simplify integration testing with external sandbox environments ## Testing Workflow Follow these steps to test network tokens: Create a network token from PAN data using standard provisioning or deterministic mode (if enabled). For detailed API documentation, refer to: * [Commerce Network Tokens API](/products/commerce/v2/api-reference/tokens/overview) * [Guardian Network Tokens API](/products/guardian/api-reference/network/create-token) Generate payment data (cryptogram) for the network token. The cryptogram is used during payment authorization as proof of token validation. Use the network token and cryptogram for payment authorization with your PSP or acquirer. Confirm the expected behavior based on your test scenario and validate that the network token and cryptogram work correctly. ## Test Cards Use the test cards provided in our [Test Cards documentation](/platform/resources/testing/test-card) to test network token scenarios. These cards support various network tokenization scenarios including: * Successful provisioning * Eligibility failures * Payment data request failures # Test Cards Source: https://developer.hellgate.io/platform/resources/testing/test-card You must not perform tests with **real** card data on your **Sandbox** account. ## Network Tokenization[​](#network-tokenization "Direct link to Network Tokenization") This section provides a list of test cards specifically designed for testing network tokenization scenarios. These cards can be used to simulate various network tokenization processes, allowing you to verify the functionality and reliability of your integration with our services. | Scenario | Card Number | Expiry Date | CVC | Expected Result | | - | - | - | - | - | | Test successful Network Tokenization | Any VISA Test-Card | Any | Any | Tokenized card with Network token status = 'Active' | | Test unsuccessful Network Tokenization (Card not eligible) | 4000000000000101 | Any | Any | Tokenized card with Network token status = 'Failed' | | Test successful Network Tokenization but failure on payment-data requests | 4000000000000002 | Any | Any | Tokenized card with Network token status = 'Active' / requests for payment-data will fail | | Scenario | Card Number | Expiry Date | CVC | Expected Result | | - | - | - | - | - | | Test successful Network Tokenization | Any Mastercard Test-Card | Any | Any | Tokenized card with Network token status = 'Active' | | Test unsuccessful Network Tokenization (Card not eligible) | 5186009200935811 | Any | Any | Tokenized card with Network token status = 'Failed' | | Test unsuccessful Network Tokenization (Card not eligible) | 5204731940398145 | Any | Any | Tokenized card with Network token status = 'Failed' | | Test unsuccessful Network Tokenization (Card not eligible) | 5120340000000392 | Any | Any | Tokenized card with Network token status = 'Failed' | | Test successful Network Tokenization but failure on payment-data requests | 2223000048430018 | Any | Any | Tokenized card with Network token status = 'Active' / requests for payment-data will fail | | Scenario | Card Number | Expiry Date | CVC | Expected Result | | - | - | - | - | - | | Test successful Network Tokenization | Any American Express Test-Card | Any | Any | Tokenized card with Network token status = 'Active' | | Test unsuccessful Network Tokenization (Card not eligible) | 378734493671000 | Any | Any | Tokenized card with Network token status = 'Failed' | | Test successful Network Tokenization but failure on payment-data requests | 371449635398431 | Any | Any | Tokenized card with Network token status = 'Active' / requests for payment-data will fail | | Scenario | Card Number | Expiry Date | CVC | Expected Result | | - | - | - | - | - | | Test successful Network Tokenization | Any Discover Test-Card | Any | Any | Tokenized card with Network token status = 'Active' | | Test unsuccessful Network Tokenization (Card not eligible) | 6011000990139440 | Any | Any | Tokenized card with Network token status = 'Failed' | | Test successful Network Tokenization but failure on payment-data requests | 6011000990139432 | Any | Any | Tokenized card with Network token status = 'Active' / requests for payment-data will fail | ### Visa Acceptance TMS Use these cards when your Test tier provisions network tokens through the [Visa Acceptance Token Management Service (TMS) sandbox](https://developer.visaacceptance.com/docs/cybs/en-us/tms/developer/all/rest/tms/tms-create-request/tms-test-cards.html) instead of the Hellgate Cloud Network Token Sandbox. Visa Acceptance documents these cards with `X` placeholders. Hellgate recognises only the exact numbers listed here. Any other digits in place of `X` are treated as a real card number and rejected on the Test tier. | Scenario | Card Number | Expiry Date | CVC | Expected Result | | - | - | - | - | - | | Test successful Network Tokenization | 4622943123100639 | Any | Any | Tokenized card with Network token status = 'Active' | | Test successful Network Tokenization | 4622943123100647 | Any | Any | Tokenized card with Network token status = 'Active' | | Test successful Network Tokenization | 4622943123100654 | Any | Any | Tokenized card with Network token status = 'Active' | | Test successful Network Tokenization | 4622943123100662 | Any | Any | Tokenized card with Network token status = 'Active' | | Test successful Network Tokenization | 4622943123100670 | Any | Any | Tokenized card with Network token status = 'Active' | | Test successful Network Tokenization | 4622943123100688 | Any | Any | Tokenized card with Network token status = 'Active' | | Test successful Network Tokenization | 4622943123100696 | Any | Any | Tokenized card with Network token status = 'Active' | | Test unsuccessful Network Tokenization (Card verification failed) | 4000000011000009 | Any | Any | Tokenized card with Network token status = 'Failed' | | Test unsuccessful Network Tokenization (Card not eligible) | 4001770011000006 | Any | Any | Tokenized card with Network token status = 'Failed' | | Test unsuccessful Network Tokenization (Card not allowed) | 4010057011000007 | Any | Any | Tokenized card with Network token status = 'Failed' | | Test unsuccessful Network Tokenization (Declined) | 4010057022000004 | Any | Any | Tokenized card with Network token status = 'Failed' | | Test unsuccessful Network Tokenization (Declined) | 4020057022000002 | Any | Any | Tokenized card with Network token status = 'Failed' | | Test unsuccessful Network Tokenization (Service unavailable) | 4010057033000001 | Any | Any | Tokenized card with Network token status = 'Failed' | | Test unsuccessful Network Tokenization (Service unavailable) | 4020057033000009 | Any | Any | Tokenized card with Network token status = 'Failed' | | Test unsuccessful Network Tokenization (System error) | 4010057044000008 | Any | Any | Tokenized card with Network token status = 'Failed' | | Test unsuccessful Network Tokenization (System error) | 4020057044000006 | Any | Any | Tokenized card with Network token status = 'Failed' | | Test unsuccessful Network Tokenization (Invalid request) | 4020057055000002 | Any | Any | Tokenized card with Network token status = 'Failed' | | Test unsuccessful token for token (Card verification failed) | 4000010011000008 | Any | Any | Tokenized card with Network token status = 'Failed' | | Test unsuccessful token for token (Card not eligible) | 4000010022000005 | Any | Any | Tokenized card with Network token status = 'Failed' | | Test unsuccessful token for token (Card not allowed) | 4000010033000002 | Any | Any | Tokenized card with Network token status = 'Failed' | | Test unsuccessful token for token (Service unavailable) | 4000010044000009 | Any | Any | Tokenized card with Network token status = 'Failed' | | Test unsuccessful token for token (System error) | 4000010055000005 | Any | Any | Tokenized card with Network token status = 'Failed' | | Test unsuccessful token for token (Invalid request) | 4000010088000006 | Any | Any | Tokenized card with Network token status = 'Failed' | | Scenario | Card Number | Expiry Date | CVC | Expected Result | | - | - | - | - | - | | Test successful Network Tokenization | 5120342233150747 | Any | Any | Tokenized card with Network token status = 'Active' | | Test successful Network Tokenization | 5120343287499758 | Any | Any | Tokenized card with Network token status = 'Active' | | Test successful Network Tokenization | 5120350100064594 | Any | Any | Tokenized card with Network token status = 'Active' | | Test successful token for token | Any Mastercard Test-Card | Any | Any | Tokenized card with Network token status = 'Active' | | Scenario | Card Number | Expiry Date | CVC | Expected Result | | - | - | - | - | - | | Test successful Network Tokenization | Any American Express Test-Card | Any | Any | Tokenized card with Network token status = 'Active' | | Test unsuccessful Network Tokenization (Card not eligible) | 370000000000028 | Any | Any | Tokenized card with Network token status = 'Failed' | | Test unsuccessful Network Tokenization (Declined) | 370000000000002 | Any | Any | Tokenized card with Network token status = 'Failed' | | Test unsuccessful Network Tokenization (Service unavailable) | 370000000000119 | Any | Any | Tokenized card with Network token status = 'Failed' | | Test unsuccessful Network Tokenization (Card not allowed) | 370000000000036 | Any | Any | Tokenized card with Network token status = 'Failed' | ## 3-D Secure[​](#3-d-secure "Direct link to 3-D Secure") In this section, you'll find a list of test cards designed to simulate various scenarios, allowing for comprehensive testing of your integration with our services. | Scenario | Card Number | Expiry Date | CVC | Expected Result | | - | - | - | - | - | | Test successful Tokenization with ID\&V (Challenge Authentication) | 4100000000000100 | Any | Any | Tokenized card with 3DS challenge. Enter the password "123456" to complete the transaction | | Test successful Tokenization with ID\&V (Challenge Authentication) | 4100000000600008 | Any | Any | Tokenized card with 3DS challenge. Enter the password "123456" to complete the transaction | | Test successful Tokenization with ID\&V (Challenge Authentication) | 4100000000005000 | Any | Any | Tokenized card with 3DS challenge. Enter the password "123456" to complete the transaction | | Test successful Tokenization with ID\&V (Challenge Authentication) | 4100000000100009 | Any | Any | Tokenized card with 3DS challenge. Enter the password "123456" to complete the transaction | | Test unsuccessful Tokenization with ID\&V (Authentication Failed) | 4100000000300005 | Any | Any | No token issued with 3DS challenge. Enter the password "111111" to simulate the cardholder not being authenticated | | Test unsuccessful Tokenization with ID\&V (Authentication Unavailable) | 4100000000400003 | Any | Any | No token issued with authentication unavailable, due to a simulated technical error with the ACS (Access Control Server) | | Test unsuccessful Tokenization with ID\&V (Authentication Rejected) | 4100000000500000 | Any | Any | No token issued with the authentication being rejected by the ACS (Access Control Server) | | Scenario | Card Number | Expiry Date | CVC | Expected Result | | - | - | - | - | - | | Test successful Tokenization with ID\&V (Challenge Authentication) | 2222690420064590 | 12/2030 | 123 | Tokenized card with 3DS challenge. Enter the password "123456" to complete the transaction | | Test successful Tokenization with ID\&V (Challenge Authentication) | 5100000000000107 | Any. | Any | Tokenized card with 3DS challenge. Enter the password "123456" to complete the transaction | | Test successful Tokenization with ID\&V (Challenge Authentication) | 5100000000600005 | Any. | Any | Tokenized card with 3DS challenge. Enter the password "123456" to complete the transaction | | Test successful Tokenization with ID\&V (Challenge Authentication) | 5100000000005007 | Any. | Any | Tokenized card with 3DS challenge. Enter the password "123456" to complete the transaction | | Test successful Tokenization with ID\&V (Challenge Authentication) | 5100000000100006 | Any. | Any | Tokenized card with 3DS challenge. Enter the password "123456" to complete the transaction | | Test unsuccessful Tokenization with ID\&V (Authentication Failed) | 5100000000300002 | Any. | Any | No token issued with challenge. Enter the password "111111" to simulate the cardholder not being authenticated | | Test unsuccessful Tokenization with ID\&V (Authentication Unavailable) | 5100000000400000 | Any. | Any | No token issued with authentication unavailable, due to a simulated technical error with the ACS (Access Control Server) | | Test unsuccessful Tokenization with ID\&V (Authentication Rejected) | 5100000000500007 | Any. | Any | No token issued with the authentication being rejected by the ACS (Access Control Server) | # Card Tokenization Source: https://developer.hellgate.io/platform/resources/testing/web-sdk Exercise the Web SDK card flow with a session, no front-end required. The [Hellgate Cloud Web SDK Demo](https://demo.hellgate.dev/card) is a hosted front-end that drives the Web SDK flows end to end. Paste a `session_id` from your server, submit a card, and confirm the resulting token — all without building a browser integration first. Reach for it when you want to verify a session from your backend before wiring up the SDK in your own application. The workbench covers four flows, exposed as tabs at the top of the page: **Card Tokenization**, **CVC2 Refresh**, **Card Elements**, and **3DS**. This page walks through the default **Card Tokenization** flow; the other tabs consume the same session and backend configuration. ## Prerequisites A `session_id` from your backend — see the [Guardian](/products/guardian/api-reference/pci/create-token) and [Commerce v2](/products/commerce/v2/api-reference/tokens_create/request-a-token-session) examples on the right. ```bash Guardian theme={null} curl --request POST \ --url https://.eu1.on-hellgate.cloud/api/pci/tokens \ --header 'content-type: application/json' \ --header 'authorization: Bearer ' \ --data '{ "source": "session" }' ``` ```bash Commerce v2 theme={null} curl --request POST \ --url https://sandbox.hellgate.io/tokens/session \ --header 'content-type: application/json' \ --header 'x-api-key: hlg-sbx-...' ``` ```json Guardian theme={null} { "id": "a9f4e2c0-12d0-4b73-8e8c-0f0f0f0f0f0f", "session_id": "7f2c9460-7e2e-403f-a08a-bc58cdcaea83", "status": "pending" } ``` ```json Commerce v2 theme={null} { "session_id": "7f2c9460-7e2e-403f-a08a-bc58cdcaea83" } ``` ## Steps Visit [https://demo.hellgate.dev/card](https://demo.hellgate.dev/card). The page loads the same Web SDK you would embed in your own application. Use the **Backend Service URL** dropdown at the top of the page to match the environment that issued your session. There are built-in options (e.g. **Sandbox (Shared Commerce v2)**), which target the Test tier of the shared Hellgate Commerce v2 platform. For any other deployment — a dedicated Commerce v2 instance or a Guardian instance — enable the **Custom URL** toggle and enter the base URL of your backend (for example `https://.eu1.on-hellgate.cloud`). Paste the `session_id` into the **Session ID** field. The remaining controls change how the SDK renders the card form. Leave them at their defaults unless you want to preview a specific layout: * **Schemes** — restrict the accepted card networks. * **Card Layout** — render the card fields as **Single-line** or **Multi-line**. * **Expiration Fields** — **Combined** month/year field or **Separate** fields. * **Save my card** — toggles the `ephemeral` flag passed to `process()`, so you can exercise the [token lifecycle](/platform/resources/sdks/web-sdk/api#token-lifecycle) consent flow end-to-end. Click **Test Card**, fill in the card details in the rendered form, and submit. On success the workbench shows the resulting token. Confirm the same record through your own integration — for example by fetching it through the relevant product's API or viewing it in the Hellgate dashboard. ## Next steps Once the workbench flow succeeds, port the same session-plus-SDK pattern into your own application: * [Web SDK Overview](/platform/resources/sdks/web-sdk/overview) — initialize the SDK and render the card form. * [Web SDK API](/platform/resources/sdks/web-sdk/api) — detailed handler and event reference. # Create API key Source: https://developer.hellgate.io/products/guardian/api-reference/apikey/create-api-key /products/guardian/openapi.yaml post /api/admin/api-keys Creates a new API key with the specified scopes and expiry defined via `expires_in` (seconds). # Delete API key Source: https://developer.hellgate.io/products/guardian/api-reference/apikey/delete-api-key /products/guardian/openapi.yaml delete /api/admin/api-keys/{id} Deletes a specific API key. # Get API key details Source: https://developer.hellgate.io/products/guardian/api-reference/apikey/get-api-key-details /products/guardian/openapi.yaml get /api/admin/api-keys/{id} Retrieves detailed information about a specific API key. # Get API keys Source: https://developer.hellgate.io/products/guardian/api-reference/apikey/get-api-keys /products/guardian/openapi.yaml get /api/admin/api-keys Retrieves a list of all API keys. # Update API key Source: https://developer.hellgate.io/products/guardian/api-reference/apikey/update-api-key /products/guardian/openapi.yaml patch /api/admin/api-keys/{id} Updates the scopes of an existing API key. # Event notification Source: https://developer.hellgate.io/products/guardian/api-reference/event-notification /products/guardian/openapi.yaml webhook event_notification This webhook is sent by the server to the client's registered URL when a subscribed event occurs. # Create token Source: https://developer.hellgate.io/products/guardian/api-reference/generic/create-token /products/guardian/openapi.yaml post /api/generic/tokens Create a new token in the generic scope. This type of token can be used for various use cases that do not involve sensitive card payment credentials. # Delete a token Source: https://developer.hellgate.io/products/guardian/api-reference/generic/delete-a-token /products/guardian/openapi.yaml delete /api/generic/tokens/{id} Remove a token from the system. # Get token details Source: https://developer.hellgate.io/products/guardian/api-reference/generic/get-token-details /products/guardian/openapi.yaml get /api/generic/tokens/{id} Get a token by its identifier. # Get token payload Source: https://developer.hellgate.io/products/guardian/api-reference/generic/get-token-payload /products/guardian/openapi.yaml get /api/generic/tokens/{id}/payload Retrieve the payload associated with a specific generic token. # Get tokens Source: https://developer.hellgate.io/products/guardian/api-reference/generic/get-tokens /products/guardian/openapi.yaml get /api/generic/tokens List all tokens in the generic scope. # API Information Source: https://developer.hellgate.io/products/guardian/api-reference/getting-started Essential information for using the Guardian API This reference is essential for your comprehensive understanding of Guardian and successful integration with our secure card data vault service. Every endpoint on this reference, generated from the same OpenAPI specification and grouped exactly as the navigation here. Duplicate the **Dedicated instance — template** environment and set `instance` and `env`. The Authorization tab is pre-configured for the OAuth2 client-credentials grant, so add `clientId`, `clientSecret`, `audiences` and `scopes` to the environment and hit **Get New Access Token**. Both `audiences` and `scopes` are space-delimited lists — name every instance the token may call. See [Authentication](/platform/authentication). Guardian APIs make use of RESTful conventions when possible and where it makes sense. All calls use the standard HTTP verbs to express access semantics, like `GET`, `POST`, `PATCH`, and `DELETE`. Other related conventions for our API can be found in the section below. ## JSON Conventions * Resources are addressable by a UUIDv4 `id` property. * Property names are always in `snake_case`. * Temporal data is encoded in [ISO 8601](https://en.wikipedia.org/wiki/ISO_8601) strings. ### Empty Strings and Omitted Fields The empty string is not a value. A string field is either **omitted** or present with a **real value** — and where a field is nullable, an explicit `null` unsets it. Never send `""`. Where a field is validated as a string, `""` is rejected with **422 Unprocessable Entity** and a field error on that path (see [Request Errors](#request-errors) for the response format). Not every field carries that validation, so do not treat the rejection as a safety net: where it is absent, `""` is accepted and then stored or forwarded verbatim — an empty string travels further into a request than a missing field ever does. ## Authentication Guardian authenticates every request with an **OAuth2 access token** — a signed JWT — passed as a bearer token. Request the token with the client-credentials grant against the platform [Authentication API](/platform/api-reference/overview); the token's audience is your instance name, and its scopes gate the endpoints it may call. ```bash theme={null} curl --header 'Authorization: Bearer ' \ --request POST 'https://my-instance.eu1.on-hellgate.cloud/api/...' ``` Administrative endpoints use the same token carrying the relevant `admin:*` scope. See [Authentication](/products/guardian/authentication) for the full scope reference. ### API keys (deprecated) Guardian still accepts a long-lived instance key in the `x-api-key` header, so existing integrations keep working. New integrations should use an access token instead. ```bash theme={null} curl --header 'x-api-key: ' \ --request POST 'https://my-instance.eu1.on-hellgate.cloud/api/...' ``` The `x-admin-token` header it used to pair with has been replaced by scoped access tokens. Credentials must be handled with care and kept secure. Never hardcode tokens or API keys in your source code — keep them solely on your backend systems. ## API Use ### Instance URL Guardian is provided as a service of the Hellgate Cloud Platform implementing the [Composable Payment Architecture (CPA)](/platform/cpa). Each instance is accessible at a unique host: ``` https://{instance}.eu1.on-hellgate.cloud ``` `{instance}` is your unique instance slug and `eu1` is the current environment; both are provided during onboarding. ### Pagination Endpoints that return lists of objects support pagination. Guardian uses simple offset-based pagination with the following query parameters: | Parameter | Type | Description | | - | - | - | | `limit` | `integer` | The maximum amount of objects to be returned per request. Default is 50. | | `after` | `string` (uuid) | The id of the element from which on-wards the new list is determined. It is typically the last element from the previous list. | Example request: ```bash theme={null} curl --header 'Authorization: Bearer ' \ 'https://my-instance.eu1.on-hellgate.cloud/api/pci/tokens?limit=25&after=08f4b968-259a-4989-b5ab-09ef9414f983' ``` The response includes pagination metadata: ```json theme={null} { "data": [...], "links": { "next": "https://my-instance.eu1.on-hellgate.cloud/api/pci/tokens?limit=25&after=605d229d-fc8a-4017-b115-2e606031bd79", } } ``` ### Request Errors Guardian uses standard HTTP status codes to indicate client errors on the API level. The response payload for processing errors follows a standard format. ```json theme={null} { "status": "the HTTP status code", "classifier": "the classifier of the error", "message": "interesting for humans..." } ``` The response payload for validation errors follows a standard format which includes the validation errors. ```json theme={null} { "status": "the HTTP status code", "classifier": "the classifier of the error", "message": "interesting for humans...", "validation_errors": [ { "path": "a JSON path pointing to the wrong data", "message": "something interesting for humans..." } ] } ``` ### Security Considerations Guardian handles sensitive payment card data and requires strict security practices: * All communication must use HTTPS * Access tokens, and any remaining API keys, must be stored securely * PCI compliance requirements apply based on your integration approach * Session-based tokenization (SAQ-A+) is recommended over direct PAN handling (SAQ-D+) ## Data Classification Fields that carry regulated data are marked with a classification pill next to the parameter name in this reference. The marker tells you what kind of data the field holds, so you can see at a glance which parts of a payload attract handling obligations before you write any code against them. | Marker | The field carries | | - | - | | pci-pan | The primary account number — a card number, or a value from which one can be reconstructed. | | pci-sad | Sensitive authentication data — security codes and the equivalent authentication values issued for a token. | | pii | Personal data under the GDPR — any information relating to an identified or identifiable person, not only the cardholder. | Most fields carry no marker at all. The absence of one is a statement that the field holds none of the categories above — not that the field has yet to be reviewed. Masked and truncated card numbers are the common example: `411111 •••••• 1111` identifies nobody on its own, so it carries no marker. The marker is also a gate, not just a label. Sending values into `pci-pan` or `pci-sad` fields requires your PCI DSS certification on file at **SAQ-D** or a full Report on Compliance (RoC). Sending [personal data](https://eur-lex.europa.eu/eli/reg/2016/679/oj) into `pii` fields requires a data processing agreement (DPA) between us. Contact [support@hellgate.io](mailto:support@hellgate.io) to put either in place or to learn more. # Create inquiry Source: https://developer.hellgate.io/products/guardian/api-reference/metadata/create-inquiry /products/guardian/openapi.yaml post /api/metadata/inquiries Create a new inquiry for card metadata based on either a PAN, an existing PCI token, or a network token. # Create token Source: https://developer.hellgate.io/products/guardian/api-reference/network/create-token /products/guardian/openapi.yaml post /api/network/tokens Create and provision a new network token with a card scheme. See [Network Tokens overview](/products/guardian/tokens/network-tokens) for creation sources and supported schemes. Promoting a wallet token that was already promoted returns `422` with the classifier `WALLET_TOKEN_ALREADY_PROMOTED`; promoting a token that carries no funding PAN (a `CRYPTOGRAM_3DS` token) returns `422` with `UNSUPPORTED_WALLET_TYPE`. See [Promotion](/products/guardian/tokens/wallet-tokens/google-pay#promotion) for eligibility. # Delete token Source: https://developer.hellgate.io/products/guardian/api-reference/network/delete-token /products/guardian/openapi.yaml delete /api/network/tokens/{id} Deletes a specific network token by its ID. # Forward cryptogram Source: https://developer.hellgate.io/products/guardian/api-reference/network/forward-cryptogram /products/guardian/openapi.yaml post /api/network/tokens/{id}/forward Forward a request to a certified third-party provider with cryptogram data injected server-side, so it never touches your systems. This lets SAQ-A merchants use network tokens without ever handling a raw cryptogram, and mirrors [Forward card data](/products/guardian/api-reference/pci/forward-card-data). - Resolves the single-use `x-cryptogram-reference` returned by [Request cryptogram](/products/guardian/api-reference/network/request-cryptogram) in `reference` mode. - Injects the cryptogram and other PCI-scoped fields (token PAN, expiry, ECI, and more) into your request body via placeholders. - Forwards the result to the `x-destination-url`. See [Network Tokens](/products/guardian/tokens/network-tokens#forwarding-cryptograms) for the full placeholder list. # Get card art Source: https://developer.hellgate.io/products/guardian/api-reference/network/get-card-art /products/guardian/openapi.yaml get /api/network/tokens/{id}/card-art Get the card art associated with the network token. # Get token details Source: https://developer.hellgate.io/products/guardian/api-reference/network/get-token-details /products/guardian/openapi.yaml get /api/network/tokens/{id} Retrieves details of a specific network token by its ID. # Get tokens Source: https://developer.hellgate.io/products/guardian/api-reference/network/get-tokens /products/guardian/openapi.yaml get /api/network/tokens List all network tokens. # Request cryptogram Source: https://developer.hellgate.io/products/guardian/api-reference/network/request-cryptogram /products/guardian/openapi.yaml post /api/network/tokens/{id}/cryptograms Request a cryptogram (TAVV) for a network token. Supports `ecom` (e-commerce) and `dauth` (delegated authentication). Set `mode` to control how the cryptogram is returned. `inline` returns the full cryptogram payload (SAQ-D / RoC only). `reference` returns a short-lived, single-use `cryptogram_reference` that you forward with [Forward cryptogram](/products/guardian/api-reference/network/forward-cryptogram). When omitted, `mode` defaults to `inline` on SAQ-D / RoC and is forced to `reference` on SAQ-A / SAQ-A EP. See [Network Tokens overview](/products/guardian/tokens/network-tokens). # Check security code Source: https://developer.hellgate.io/products/guardian/api-reference/pci/check-security-code /products/guardian/openapi.yaml get /api/pci/tokens/{id}/security-code Check if the security code is still available for the token. # Create token Source: https://developer.hellgate.io/products/guardian/api-reference/pci/create-token /products/guardian/openapi.yaml post /api/pci/tokens Create a new token in the PCI DSS scope. See [PCI Tokens overview](/products/guardian/tokens/pci-tokens) for creation sources (session vs pan) and compliance levels. # Delete a token Source: https://developer.hellgate.io/products/guardian/api-reference/pci/delete-a-token /products/guardian/openapi.yaml delete /api/pci/tokens/{id} Remove a token from the system. # Delete security code Source: https://developer.hellgate.io/products/guardian/api-reference/pci/delete-security-code /products/guardian/openapi.yaml delete /api/pci/tokens/{id}/security-code Remove the security code for the token. # Forward card data Source: https://developer.hellgate.io/products/guardian/api-reference/pci/forward-card-data /products/guardian/openapi.yaml post /api/pci/tokens/{id}/forward This endpoint allows to securely forward cardholder data to a certified third-party provider. It will inject sensitive cardholder data from the PCI token into the request before forwarding it. # Get token details Source: https://developer.hellgate.io/products/guardian/api-reference/pci/get-token-details /products/guardian/openapi.yaml get /api/pci/tokens/{id} Get a token by its identifier. # Get tokens Source: https://developer.hellgate.io/products/guardian/api-reference/pci/get-tokens /products/guardian/openapi.yaml get /api/pci/tokens List all tokens in the PCI DSS scope. # Refresh security code Source: https://developer.hellgate.io/products/guardian/api-reference/pci/refresh-security-code /products/guardian/openapi.yaml post /api/pci/tokens/{id}/security-code Request a refresh of the security code for the token. # Create type Source: https://developer.hellgate.io/products/guardian/api-reference/types/create-type /products/guardian/openapi.yaml post /api/admin/types Create a type on Guardian, which can be used to define a schema for generic tokens. # Delete a type Source: https://developer.hellgate.io/products/guardian/api-reference/types/delete-a-type /products/guardian/openapi.yaml delete /api/admin/types/{id} Remove a type from the system. # Get type details Source: https://developer.hellgate.io/products/guardian/api-reference/types/get-type-details /products/guardian/openapi.yaml get /api/admin/types/{id} Get a type by its identifier. # Get types Source: https://developer.hellgate.io/products/guardian/api-reference/types/get-types /products/guardian/openapi.yaml get /api/admin/types List all types. # Create certificates Source: https://developer.hellgate.io/products/guardian/api-reference/wallet/create-certificates /products/guardian/openapi.yaml post /api/wallet/apple-pay/certificates Generate the certificate signing requests (CSRs) Guardian needs for Apple Pay. Guardian creates both an EC **payment processing** CSR and an RSA **merchant identity** CSR and keeps the private keys inside its PCI boundary. Submit each `csr_pem` to Apple in the [Apple Developer](https://developer.apple.com/account/resources/certificates/list) portal, then return the signed certificates with [Upload an Apple Pay certificate](/products/guardian/api-reference/wallet/upload-a-certificate). Available only on `self_managed` instances. On Starfish-operated (`sf_operated`) instances certificates are managed by Starfish, and this endpoint returns `409` with the classifier `APPLE_PAY_CERT_MANAGEMENT_UNAVAILABLE`. # Delete a certificate Source: https://developer.hellgate.io/products/guardian/api-reference/wallet/delete-a-certificate /products/guardian/openapi.yaml delete /api/wallet/apple-pay/certificates/{id} Delete an Apple Pay certificate. Only a terminal (`retired` or `disabled`) certificate can be deleted; deleting an `active` or `pending` certificate returns `409` with the classifier `CERTIFICATE_NOT_TERMINAL`. # Delete a domain Source: https://developer.hellgate.io/products/guardian/api-reference/wallet/delete-a-domain /products/guardian/openapi.yaml delete /api/wallet/apple-pay/domains/{id} Remove a domain from Apple Pay. The domain can no longer present the Apple Pay button until re-registered and verified. # Delete a wallet token Source: https://developer.hellgate.io/products/guardian/api-reference/wallet/delete-a-wallet-token /products/guardian/openapi.yaml delete /api/wallet/{id} Delete a wallet token. The stored card data is scrubbed immediately; the token record itself is kept with `deleted_at` set and is returned in the response. A deleted token can no longer be forwarded or promoted. # Disable a certificate Source: https://developer.hellgate.io/products/guardian/api-reference/wallet/disable-a-certificate /products/guardian/openapi.yaml post /api/wallet/apple-pay/certificates/{id}/disable Disable an active Apple Pay certificate so Guardian stops using it, without deleting the record. # Forward wallet token Source: https://developer.hellgate.io/products/guardian/api-reference/wallet/forward-wallet-token /products/guardian/openapi.yaml post /api/wallet/{id}/forward Forward a request to a certified third-party provider with the wallet token's card data injected server-side, so it never touches your systems. This mirrors [Forward card data](/products/guardian/api-reference/pci/forward-card-data) and [Forward cryptogram](/products/guardian/api-reference/network/forward-cryptogram). The available placeholders depend on the token's `auth_method`: - `CRYPTOGRAM_3DS`: `{{ dpan }}`, `{{ cryptogram }}`, `{{ eci }}`, `{{ expiry_month }}`, `{{ expiry_year }}`. The cryptogram is single-use — the token is consumed by the first successful forward. - `PAN_ONLY`: `{{ fpan }}`, `{{ expiry_month }}`, `{{ expiry_year }}`. Forwardable repeatedly. Using a placeholder the token's `auth_method` does not carry returns `422` with the classifier `DESTINATION_POLICY_VIOLATION`; a declared placeholder without a value on this specific token returns `422` with `PLACEHOLDER_VALUE_MISSING`. Forwarding an already-consumed `CRYPTOGRAM_3DS` token returns `400` with `CRYPTOGRAM_CONSUMED`. See [Wallet Tokens](/products/guardian/tokens/wallet-tokens/overview#forwarding-wallet-tokens) for details. # Get association file Source: https://developer.hellgate.io/products/guardian/api-reference/wallet/get-association-file /products/guardian/openapi.yaml get /.well-known/apple-developer-merchantid-domain-association.txt Serve the Apple-issued domain-association file for this instance. This endpoint is public and unauthenticated: Apple fetches it while verifying a registered domain. Host it from every domain that presents the Apple Pay button. The same file is also served without the `.txt` extension, at `/.well-known/apple-developer-merchantid-domain-association`. If no domain-association content is configured yet, the request returns `400` with the classifier `APPLE_PAY_NOT_CONFIGURED`. # Get audit events Source: https://developer.hellgate.io/products/guardian/api-reference/wallet/get-audit-events /products/guardian/openapi.yaml get /api/wallet/apple-pay/certificates/{id}/audit-events List the audit events recorded for an Apple Pay certificate, such as activation, retirement, and use. # Get certificate details Source: https://developer.hellgate.io/products/guardian/api-reference/wallet/get-certificate-details /products/guardian/openapi.yaml get /api/wallet/apple-pay/certificates/{id} Get an Apple Pay certificate by its identifier. # Get certificates Source: https://developer.hellgate.io/products/guardian/api-reference/wallet/get-certificates /products/guardian/openapi.yaml get /api/wallet/apple-pay/certificates List the Apple Pay payment processing certificates registered on this instance. # Get domain details Source: https://developer.hellgate.io/products/guardian/api-reference/wallet/get-domain-details /products/guardian/openapi.yaml get /api/wallet/apple-pay/domains/{id} Get an Apple Pay domain by its identifier. # Get domains Source: https://developer.hellgate.io/products/guardian/api-reference/wallet/get-domains /products/guardian/openapi.yaml get /api/wallet/apple-pay/domains List the domains registered for Apple Pay on this instance. # Get settings Source: https://developer.hellgate.io/products/guardian/api-reference/wallet/get-settings /products/guardian/openapi.yaml get /api/wallet/apple-pay/settings Return the operating `mode` of this instance, along with the settings uploaded via [Update settings](/products/guardian/api-reference/wallet/update-settings). All fields other than `mode` are `null` until the first upload, and always `null` on `sf_operated` instances, where the Apple identity is mounted by Starfish and not exposed through this endpoint. # Get trust anchor Source: https://developer.hellgate.io/products/guardian/api-reference/wallet/get-trust-anchor /products/guardian/openapi.yaml get /api/wallet/apple-pay/trust-anchors Return the active Apple Root CA trust anchor on this instance. # Get wallet token details Source: https://developer.hellgate.io/products/guardian/api-reference/wallet/get-wallet-token-details /products/guardian/openapi.yaml get /api/wallet/{id} Get a wallet token by its identifier. # Get wallet tokens Source: https://developer.hellgate.io/products/guardian/api-reference/wallet/get-wallet-tokens /products/guardian/openapi.yaml get /api/wallet List all wallet tokens. # Register a domain Source: https://developer.hellgate.io/products/guardian/api-reference/wallet/register-a-domain /products/guardian/openapi.yaml post /api/wallet/apple-pay/domains Register a domain that will present the Apple Pay button. On `self_managed` instances the domain is marked `verified` immediately: you attest domain ownership to Apple yourself by registering the domain in the Apple Developer portal, where Apple fetches the domain-association file you serve at `/.well-known/apple-developer-merchantid-domain-association.txt`. On `sf_operated` instances Guardian instead schedules a background verification that registers the domain with Apple's Web Merchant Registration API using the platform integrator identity, and the domain starts as `pending`. If the verification attempt cannot be scheduled, the request returns `503` with the classifier `ENQUEUE_FAILED`; retry the request. # Replace trust anchor Source: https://developer.hellgate.io/products/guardian/api-reference/wallet/replace-trust-anchor /products/guardian/openapi.yaml post /api/wallet/apple-pay/trust-anchors Replace the active Apple Root CA trust anchor Guardian verifies payment payloads against. The instance ships with Apple's current root CA seeded, so you only need this if Apple rotates its root certificate. The previous anchor is superseded atomically. A PEM that cannot be decoded as a valid X.509 certificate returns `422` with the classifier `APPLE_PAY_INVALID_TRUST_ANCHOR`. # Retire a certificate Source: https://developer.hellgate.io/products/guardian/api-reference/wallet/retire-a-certificate /products/guardian/openapi.yaml post /api/wallet/apple-pay/certificates/{id}/retire Retire an Apple Pay certificate that is no longer in use, for example after rotating to a newer one. # Tokenize Apple Pay Source: https://developer.hellgate.io/products/guardian/api-reference/wallet/tokenize-apple-pay /products/guardian/openapi.yaml post /api/wallet/apple-pay/tokenize Turn an Apple Pay payment into a stored wallet token. Pass the `PKPaymentToken` `paymentData` envelope exactly as the Apple Pay JS API returns it, as the `payment_data` field. Guardian verifies the `EC_v1` signature against Apple's trusted certificates and decrypts the payload server-side with your active payment processing certificate, so the decrypted card data never touches your systems. An Apple Pay payment always yields a device-bound token: a device PAN (DPAN) with a single-use cryptogram and ECI. The underlying funding PAN is never extracted, stored, or returned, so an Apple Pay token cannot be promoted to a network token. Tokenization is idempotent on the encrypted payload: resubmitting the same envelope returns the existing wallet token. If that payload's token was already deleted, the request returns `409` with the classifier `APPLE_PAY_TOKEN_DELETED`. Rejected envelopes return `422` with a classifier naming the cause: `APPLE_PAY_SIGNATURE_INVALID`, `APPLE_PAY_UNKNOWN_CERTIFICATE`, `APPLE_PAY_TOKEN_EXPIRED`, or `APPLE_PAY_MALFORMED_TOKEN` (Guardian's generic missing-field classifier, shared by the other Apple Pay endpoints). If no active payment processing certificate exists, the request returns `500` with the classifier `APPLE_PAY_NO_ACTIVE_CERTIFICATE`. See [Apple Pay](/products/guardian/tokens/wallet-tokens/apple-pay) for prerequisites and the full flow. # Tokenize Google Pay Source: https://developer.hellgate.io/products/guardian/api-reference/wallet/tokenize-google-pay /products/guardian/openapi.yaml post /api/wallet/google-pay/tokenize Turn a Google Pay payment into a stored wallet token. Pass the `PaymentMethodToken` envelope exactly as Google Pay returns it — Guardian verifies the ECv2 signature chain and decrypts the payload server-side, so the decrypted card data never touches your systems. The resulting wallet token carries one of two authentication methods, reflected in the `auth_method` field: - `CRYPTOGRAM_3DS` — a device-bound token with DPAN and cryptogram, forwardable once. - `PAN_ONLY` — a card-on-file token with the funding PAN, forwardable repeatedly and eligible for [promotion to a network token](/products/guardian/tokens/wallet-tokens/google-pay#promotion). Submitting the same envelope twice returns `409` with the classifier `GOOGLE_PAY_DUPLICATE_PAYLOAD`. Rejected envelopes return `422` with a classifier naming the cause: `GOOGLE_PAY_SIGNATURE_INVALID`, `GOOGLE_PAY_DECRYPTION_FAILED`, `GOOGLE_PAY_TOKEN_EXPIRED`, or `GOOGLE_PAY_MALFORMED_TOKEN`. See [Google Pay](/products/guardian/tokens/wallet-tokens/google-pay) for prerequisites and the full flow. # Update settings Source: https://developer.hellgate.io/products/guardian/api-reference/wallet/update-settings /products/guardian/openapi.yaml put /api/wallet/apple-pay/settings Upload your Apple identity so Guardian can generate CSRs, serve the domain-association file, and validate merchant sessions. Provide your `merchant_identifier`, a `display_name`, the byte-exact `domain_association_content` Apple issued, and the `csr_common_name` to embed in generated CSRs. The upload is a partial update: fields you omit keep their current values, and no field is individually required. All four must be set before the dependent flows work. Available only on `self_managed` instances. On Starfish-operated (`sf_operated`) instances the Apple identity and certificates are managed by Starfish, and this endpoint returns `409` with the classifier `APPLE_PAY_CERT_MANAGEMENT_UNAVAILABLE`. # Upload a certificate Source: https://developer.hellgate.io/products/guardian/api-reference/wallet/upload-a-certificate /products/guardian/openapi.yaml post /api/wallet/apple-pay/certificates/{id}/upload Return the signed certificate chain Apple issued for a CSR created with [Create Apple Pay certificates](/products/guardian/api-reference/wallet/create-certificates). Once uploaded, the certificate moves to `active` and becomes the one Guardian uses to decrypt payloads and authenticate to Apple. # Validate Apple Pay session Source: https://developer.hellgate.io/products/guardian/api-reference/wallet/validate-apple-pay-session /products/guardian/openapi.yaml post /api/wallet/apple-pay/sessions Complete Apple Pay's `onvalidatemerchant` exchange. When the customer opens the Apple Pay sheet in a browser, Apple fires `onvalidatemerchant`; your backend forwards the request here. Guardian performs the mTLS handshake with Apple using your merchant identity certificate and returns Apple's merchant session object verbatim, which you pass to `session.completeMerchantValidation(...)` in the browser. The `domain_name` must be a verified [Apple Pay domain](/products/guardian/tokens/wallet-tokens/apple-pay#prerequisites). Guardian reads your merchant identifier and display name from the [Apple Pay settings](/products/guardian/api-reference/wallet/get-settings); until those are uploaded the request returns `400` with the classifier `APPLE_PAY_NOT_CONFIGURED`. A request for an unregistered or unverified domain returns `422` with the classifier `APPLE_PAY_DOMAIN_NOT_VERIFIED`. If Apple rejects the validation, Guardian returns `502` with `APPLE_PAY_SESSION_ERROR`; if no active merchant identity certificate exists, the request returns `500` with `APPLE_PAY_NO_ACTIVE_CERTIFICATE`. # Verify a domain Source: https://developer.hellgate.io/products/guardian/api-reference/wallet/verify-a-domain /products/guardian/openapi.yaml post /api/wallet/apple-pay/domains/{id}/verify Re-verify a `pending` or `failed` domain, for example a row created before a mode switch or a failed `sf_operated` attempt. A domain that is already `verified` returns `409` with the classifier `DOMAIN_NOT_RETRIABLE`. On `self_managed` instances the domain is marked `verified` immediately, trusting your own attestation of domain ownership to Apple. On `sf_operated` instances Guardian schedules a background verification against Apple's Web Merchant Registration API using the platform integrator identity; if the attempt cannot be scheduled, the request returns `503` with the classifier `ENQUEUE_FAILED`. # Delete webhook Source: https://developer.hellgate.io/products/guardian/api-reference/webhook/delete-webhook /products/guardian/openapi.yaml delete /api/admin/webhooks/{id} Deletes a specific webhook. # Get webhook details Source: https://developer.hellgate.io/products/guardian/api-reference/webhook/get-webhook-details /products/guardian/openapi.yaml get /api/admin/webhooks/{id} Retrieves detailed information about a specific webhook. # Get webhooks Source: https://developer.hellgate.io/products/guardian/api-reference/webhook/get-webhooks /products/guardian/openapi.yaml get /api/admin/webhooks List all webhooks known to the system. # Register webhook Source: https://developer.hellgate.io/products/guardian/api-reference/webhook/register-webhook /products/guardian/openapi.yaml post /api/admin/webhooks Register a new webhook and receive callbacks. # Authentication Source: https://developer.hellgate.io/products/guardian/authentication Authenticate to your Guardian instance with an OAuth2 access token and the scopes that gate each endpoint. Once your Guardian instance is provisioned (see [Plans & Access](/products/guardian/plans-and-access)), you reach the API at your dedicated instance URL, where `{instance}` is your unique instance slug and `eu1` is the current environment; both are provided during onboarding: ``` https://{instance}.eu1.on-hellgate.cloud ``` ## Authentication Every API request must carry an OAuth2 access token — a signed JSON Web Token (JWT) — in the `Authorization` header: ```http theme={null} Authorization: Bearer ``` You request the token with the client-credentials grant — see [Authentication](/platform/authentication) for the token endpoint, audiences, and scopes. Your instance's audience is its instance name. **API keys are deprecated.** Guardian still accepts a long-lived instance key in the `x-api-key` header, so existing integrations keep working, but new integrations should use an access token. The `x-admin-token` header has been replaced — administrative endpoints now use the same access token carrying the relevant `admin:*` scope. ## Scopes Each endpoint requires a specific scope. Request only the scopes a given integration needs through the `scope` parameter when you [request a token](/platform/authentication) — follow the principle of least privilege. ### Token operations | Scope | Grants | | - | - | | `pci:tokens:create` | `POST /api/pci/tokens` | | `pci:tokens:read` | `GET /api/pci/tokens`
`GET /api/pci/tokens/{id}`
`GET /api/pci/tokens/{id}/security-code` | | `pci:tokens:update` | `POST /api/pci/tokens/{id}/security-code`
`DELETE /api/pci/tokens/{id}/security-code` | | `pci:tokens:delete` | `DELETE /api/pci/tokens/{id}` | | `pci:tokens:forward` | `POST /api/pci/tokens/{id}/forward` | | `generic:tokens:create` | `POST /api/generic/tokens` | | `generic:tokens:read` | `GET /api/generic/tokens`
`GET /api/generic/tokens/{id}`
`GET /api/generic/tokens/{id}/payload` | | `generic:tokens:delete` | `DELETE /api/generic/tokens/{id}` | | `network:tokens:create` | `POST /api/network/tokens` | | `network:tokens:read` | `GET /api/network/tokens`
`GET /api/network/tokens/{id}`
`GET /api/network/tokens/{id}/card-art` | | `network:tokens:delete` | `DELETE /api/network/tokens/{id}` | | `network:tokens:use` | `POST /api/network/tokens/{id}/cryptograms` | | `network:tokens:forward` | `POST /api/network/tokens/{id}/forward` | | `metadata:inquiries:create` | `POST /api/metadata/inquiries` | `network:tokens:*` requires the **Network Tokens** add-on, and `metadata:inquiries:create` requires the **Metadata Inquiries** add-on, enabled on your instance. See [Plans & Access](/products/guardian/plans-and-access). ### Administrative operations Administrative endpoints manage the instance itself. They require a token carrying the relevant `admin:*` scope, and should be issued to operator or back-office clients only. | Scope | Grants | | - | - | | `admin:api-keys:create` | `POST /api/admin/api-keys` | | `admin:api-keys:read` | `GET /api/admin/api-keys`
`GET /api/admin/api-keys/{id}` | | `admin:api-keys:update` | `PATCH /api/admin/api-keys/{id}` | | `admin:api-keys:delete` | `DELETE /api/admin/api-keys/{id}` | | `admin:webhooks:create` | `POST /api/admin/webhooks` | | `admin:webhooks:read` | `GET /api/admin/webhooks`
`GET /api/admin/webhooks/{id}` | | `admin:webhooks:delete` | `DELETE /api/admin/webhooks/{id}` | | `admin:types:create` | `POST /api/admin/types` | | `admin:types:read` | `GET /api/admin/types`
`GET /api/admin/types/{id}` | | `admin:types:delete` | `DELETE /api/admin/types/{id}` | | `admin:imports:create` | `POST /api/admin/imports` | | `admin:imports:read` | `GET /api/admin/imports/{id}` | | `admin:imports:cancel` | `POST /api/admin/imports/{id}/cancel` | ## Next steps Manage the deprecated instance API keys that existing integrations still use. # Guardian Updates Source: https://developer.hellgate.io/products/guardian/changelog New releases and improvements of Guardian. ## Masked Token Number and PAR on Network Tokens The network token object now carries the masked token number (TPAN) and the Payment Account Reference: `network_token.masked_number`, `last_four`, `bin` and `payment_account_reference`. Guardian derives them from the token data it already sees at provisioning and on the cryptogram path and stores only the BIN, the last four digits and the PAR; the plaintext TPAN still never leaves the card data environment. Tokens provisioned before this release receive the digits with their next cryptogram, Mastercard tokens their PAR through a backfill; each change is announced by a `network.token.updated` webhook. [List tokens](/products/guardian/api-reference/network/get-tokens) accepts a `last_four` filter, the first filter on that endpoint, and keeps it in the pagination links. The admin token lookup shows the masked number and the PAR. See [Token Digits and PAR](/products/guardian/tokens/network-tokens#token-digits-and-par). ## Bugfixes * Forwards to a destination whose hostname is an alias (a DNS `CNAME`) are delivered again. Since the September 30 release, such forwards failed with a `500`. ## Scheme Deletions Delete the Network Token A network token the card scheme deletes is now deleted in Guardian exactly like one you delete through the API: it disappears from the token list and can no longer be read. Until now a scheme-side deletion only set the status to `deleted`, so the token stayed listed and you learned about it through a `network.token.updated` webhook. Both kinds of deletion now send a dedicated `network.token.deleted` webhook, so you no longer have to infer a deletion from a status change. ## Forwards Go Only Where They Were Checked Guardian resolves a forward's destination once, checks that address, and sends the request to exactly that address. A hostname can no longer pass the check with a public address and then resolve to an internal one when the request is sent. The destination still receives its own hostname in the `Host` header, with the port when it is not the default, including destinations reachable only over IPv6. ## Improvements and bugfixes * A custom header pair (`x-own-header-name` / `x-own-header-value`) can no longer set `Host` or `Content-Length`. Guardian sets both itself and drops a pair that names either. * A card number in `metadata` or `additional_data`, for example in `cardholder_name`, is rejected on every tier, test cards included. * Endpoints that never take card data — such as sessions, forwards, and generic tokens — reject a request that carries a card number in any field, including a card number sent as an unquoted JSON number or as a query parameter. ## Forwards Are Delivered at Most Once Forwarding — card data, cryptogram, or wallet token — now guarantees at-most-once delivery to your destination. Guardian never retries a forward on its own: a payment authorization cannot reach your endpoint twice, and a failed forward is always yours to decide about. At the same time, the sporadic `502` responses some integrations saw when a pooled connection to the destination had gone stale are gone — Guardian discards idle connections before that can happen, so the fresh guarantee does not come at the cost of more transient errors. ## Improvements and bugfixes * A session created with `expires_in` now strictly caps the resulting token's lifetime. Lifecycle arguments in the encrypted client payload — documented as ignored on such sessions — could previously extend a token's expiry past the session's TTL; they no longer can. See [PCI Tokens](/products/guardian/tokens/pci-tokens) for how session and client-side lifecycle settings interact. * Browser requests to the session endpoints now accept the Web SDK's traceability headers, so cross-origin session calls carry full request tracing. ## Forward Header Overrides and PCI Token Expiry in the Specification The OpenAPI document now declares the `x-own-header-name` / `x-own-header-value` header pair on all three forward operations — card data, cryptogram, and wallet token. Guardian strips the credential your call authenticates with before forwarding; the pair sets the destination's own credential on the forwarded request. The behavior is unchanged and was already described in the [PCI Tokens guide](/products/guardian/tokens/pci-tokens#http-headers); clients generated from the document alone could not send a destination credential before. `pci_token.expires_at` is now typed nullable: a token created without an expiry returns `"expires_at": null`, which a schema-validating client previously had to reject. Documentation only; no endpoint changed. Regenerate clients. ## Data Classification Markers on Card Fields The Guardian API reference now marks every field that carries regulated data. `account_number` and the PAN returned on a cryptogram are marked `pci-pan`; `security_code`, the cryptogram itself, and the dynamic CVV are marked `pci-sad`; `cardholder_name` is marked `pii`. The marker is a gate as well as a label: sending values into `pci-pan` or `pci-sad` fields requires your PCI DSS certification on file at SAQ-D or a full Report on Compliance. This is what makes the difference between the session-based and direct-PAN integration paths visible field by field. Fields without a marker hold none of those categories — masked and truncated card numbers are the common example. The [full legend](/products/guardian/api-reference/getting-started#data-classification) is on the API reference. Nothing about any endpoint changed — this is documentation only. ## Polymorphic Request Bodies Specified as Discriminated Unions The request bodies that accept several shapes — `source` on PCI token creation, network token creation, and metadata inquiry, plus the cryptogram request — are now specified as discriminated unions on `type`. The accepted requests are unchanged; what changes is that the specification states which `type` selects which shape, so the reference documentation shows one tab per source and generated clients get a proper tagged union. Three places where the specification was looser than the API and has been corrected: `type` is required on the cryptogram request, and both `type` and `account_number` are required on a metadata inquiry from a PAN. Requests missing them were already rejected — the document simply did not say so. Responses that vary by shape rather than by a tag — PCI token creation, network token creation, and the cryptogram response — keep their existing structure and now document how to tell the variants apart. ## Richer Network Token Provisioning Events `network_token.provisioned` events now carry the `network_token_id`, the `pci_token_id`, and the card's `last_four`, so you can reconcile provisioning activity against your token records without extra lookups. ## Improvements and bugfixes * Fixed access token verification: a valid OAuth2 access token could be rejected with `401 invalid_token`. See [Authentication](/products/guardian/authentication). ## OAuth2 Access Tokens Replace API Keys Guardian now authenticates with an OAuth2 access token, the same client-credentials flow used across the Hellgate Cloud Platform. Request a token from the [Authentication API](/platform/api-reference/overview) with your instance name as the audience, and send it as `Authorization: Bearer `. The scopes that previously sat on an API key now travel in the token. Long-lived `x-api-key` credentials are **deprecated**. Existing integrations keep working, but new ones should use access tokens. The `x-admin-token` header has been replaced — administrative endpoints now accept the same access token carrying the relevant `admin:*` scope. See [Authentication](/products/guardian/authentication). ## Card Fingerprints on PCI Tokens PCI token responses can now include `card.fingerprint`: a deterministic, non-reversible identifier of the card number, unique per Guardian instance. The same card always produces the same fingerprint — across `session` and `pan` tokenization and after a reissue with a new expiry date — so you can recognize a returning card without handling the PAN. The field is read-only and enabled per instance. See [PCI Tokens](/products/guardian/tokens/pci-tokens#card-fingerprints) for details. ## Simpler Apple Pay Domain Verification on Self-Managed Deployments On `self_managed` deployments, registering an Apple Pay domain now marks it `verified` immediately: you attest domain ownership to Apple yourself by registering the domain in the Apple Developer portal, where Apple fetches the domain-association file you serve. Guardian no longer calls Apple during registration on these deployments; only `sf_operated` deployments verify domains against Apple's Web Merchant Registration API in the background. See [Apple Pay](/products/guardian/tokens/wallet-tokens/apple-pay#prerequisites) for the updated flow. ## Apple Pay Operating Modes and Settings Guardian Apple Pay deployments now run in one of two modes. In `self_managed` mode (the default) you upload your own Apple identity and manage the certificates yourself; in `sf_operated` mode Starfish operates the Apple Developer relationship and mounts the identity and certificates for you, so the settings-upload and certificate-management endpoints are disabled and return `409` with the classifier `APPLE_PAY_CERT_MANAGEMENT_UNAVAILABLE`. Self-managed operators upload their Apple identity up front with the new [Update settings](/products/guardian/api-reference/wallet/update-settings) endpoint (`merchant_identifier`, `display_name`, `domain_association_content`, and `csr_common_name`), which Guardian reads when generating CSRs, serving the domain-association file, and validating merchant sessions. See [Apple Pay](/products/guardian/tokens/wallet-tokens/apple-pay) for the full setup. ## Wallet Tokens with Apple Pay Guardian now accepts Apple Pay payments as wallet tokens. Pass the encrypted `PKPaymentToken` payload straight to Guardian, and it verifies the `EC_v1` signature against Apple's trusted certificates, decrypts, and stores the device token server-side, so the decrypted card data never touches your systems. Guardian also proxies Apple Pay merchant-session validation, so the browser's `onvalidatemerchant` exchange completes without you holding any Apple certificates. From there you can forward the resulting [wallet token](/products/guardian/tokens/wallet-tokens/overview) to your PSP or acquirer. See [Apple Pay](/products/guardian/tokens/wallet-tokens/apple-pay) to get started. ## Apple Pay Certificate and Domain Management Guardian now manages the credentials Apple Pay needs: it generates payment processing and merchant identity CSRs with the private keys held inside its PCI boundary, accepts the certificates Apple signs, tracks their lifecycle (`pending`, `active`, `retired`, `disabled`) with per-certificate audit events, and registers and verifies the merchant domains that present the Apple Pay button. See [Apple Pay](/products/guardian/tokens/wallet-tokens/apple-pay#prerequisites) for the setup steps. ## Wallet Tokens with Google Pay Guardian now accepts device wallet payments, starting with **Google Pay**. Pass the encrypted Google Pay payload straight to Guardian — it verifies the ECv2 signature chain, decrypts, and stores the credential server-side, so the decrypted card data never touches your systems. From there you can forward the resulting [wallet token](/products/guardian/tokens/wallet-tokens/overview) to your PSP or acquirer, and promote a card-on-file (`PAN_ONLY`) token to a full network token. See [Google Pay](/products/guardian/tokens/wallet-tokens/google-pay) to get started. ## JWT Authentication A new JWT authentication lane runs in parallel to API keys. Guardian validates incoming tokens against your JWKS endpoint, letting you authenticate services with short-lived JWTs instead of, or alongside, long-lived API keys. ## Network Token Cryptogram Forwarding You can now forward a network token cryptogram by reference on `POST /api/network/tokens/{id}/forward`, so Guardian resolves the cryptogram server-side and it never touches your systems. Forward templates also expose more binds — including `dynamic_cvv` and additional network token fields — for richer downstream requests. ## Token Lifecycle Controls New token lifecycle controls let you set and manage expiration on stored tokens. Guardian accepts an `expiration_time` alias for `expires_in`, and a session's `expires_in` now consistently takes precedence over the ephemeral payload flag. ## Improvements and bugfixes * Guardian now detects PANs in non-PAN attributes, catching card data that would otherwise slip through in the wrong field. * Ingress forwarding headers are no longer relayed to destinations, keeping internal routing headers out of your outbound proxy requests. * Network token create responses now serialize `pci_token_id`. * Clearer proxy template errors: Guardian returns `422` when a placeholder resolves to a map, validates unwrap placeholders inside multi-part templates, and gives more precise messages for non-renderable placeholders. * Ephemeral sessions without an `expires_in` are handled correctly. ## Guardian Admin UI Guardian now ships with a built-in Admin UI for day-to-day token operations. Browse and manage tokens, handle API keys with proper timezone-aware expiration, and walk new operators through token creation — all backed by a Playwright end-to-end test suite so it stays solid release after release. ## Metadata Inquiry Endpoint A new `POST /metadata/inquiry` endpoint lets you look up issuer metadata on-demand. The endpoint is feature-flagged per tier, propagates the `x-request-id` end-to-end as a correlation ID, and emits a billing event tagged with the requester's contract ID — so reporting and chargeback reconciliation work out of the box. ## Improvements and bugfixes * American Express now renders with its proper scheme name in admin views. * Metadata inquiry events include `network_token_id` for richer reconciliation data. * Hellgate SDK `
``` ```javascript theme={null} import express, { Request, Response } from 'express'; const app = express(); app.get('/config', async (_: Request, res: Response): Promise => { const headers = new Headers(); headers.set('Content-Type', 'application/json'); headers.set('Accept', 'application/json'); headers.set('X-API-KEY', 'YOUR_API_KEY'); const request = new Request(process.env.HELLGATE_BACKEND + '/tokens/session', { method: 'POST', headers: headers, }); const response = await fetch(request); const data = await response.json(); if (!response.ok) { res.status(response.status).send(data); return; } const { session_id } = data; res.send({ session_id: session_id }); }); app.listen(4711, (): void => console.log(`Server listening on port ${4711}!`)); ``` ### Step 2 - Create a session In order to tokenize cardholder data, you need to create a session on the server-side. This session will be used to initialize the Web SDK on the client-side. ```javascript theme={null} app.get('/config', async (_: Request, res: Response): Promise => { const headers = new Headers(); headers.set('Content-Type', 'application/json'); headers.set('Accept', 'application/json'); headers.set('X-API-KEY', 'YOUR_API_KEY'); const request = new Request( process.env.HELLGATE_BACKEND + '/tokens/session', { method: 'POST', headers: headers } ); const response = await fetch(request); const data = await response.json(); if (!response.ok) { res.status(response.status).send(data); return; } const { session_id } = data; res.send({ session_id: session_id }); }); ``` ### Step 3 - Render the form Once you have the session, you can render the form on the client-side. ```javascript theme={null} document.addEventListener('DOMContentLoaded', async () => { const { session_id } = await fetchConfig(); if (!session_id) return; const { Hellgate } = window; const client = await Hellgate.init(session_id); const cardHandler = client.use('CARD'); const cardForm = await cardHandler.createForm({ style: cardFormStyle }); const cardHolderField = cardHandler.createTextField({ placeholder: 'Cardholder Name', }); await Promise.all([ cardForm.mount('#card-form'), cardHolderField.mount('#cardholderName'), ]); const submitButtonEl = document.getElementById('submit-button'); submitButtonEl.addEventListener('click', () => handleSubmit(cardHandler, cardHolderField)); }); ``` ### Step 4 - Tokenize cardholder data When the form is submitted, you can tokenize the cardholder data using the Web SDK. ```javascript theme={null} async function handleSubmit(cardHandler, cardholderField) { try { const result = await cardHandler.process({ cardholder_name: cardHolderField, }); console.log(result); } catch (e) { console.log(e.message); } } ``` # Network Tokens Source: https://developer.hellgate.io/products/commerce/v2/tokens/network Use network tokens from card schemes with Commerce. Network tokens are an add-on feature and may be subject to extra charges. For an overview of network tokens, how they work, and why to use them, see [Network Tokens](/platform/network-tokens). ## Provisioning In Commerce, network tokens are provisioned **automatically**. Commerce tokens can be enriched with network tokens from the card schemes: once a card payment method undergoes tokenization, and if the network tokens feature is enabled for your account, Commerce requests a network token from the card scheme and associates it with the token in the background. If this operation is successful, the network token status is updated to `active`. The network token's lifecycle is aligned with the Commerce token — you do not provision or manage it separately. ## Identifying a network token Your processor shows a network token payment by the token's own card-like number, the TPAN. To match it against a Commerce token, the token resource carries the TPAN in masked form next to the masked card number: | Field | Description | | - | - | | `masked_network_token_number` | The TPAN masked to its BIN and last four digits, for example `480012XXXXXX6777`. | | `network_token_last_four` | Last four digits of the TPAN. | | `payment_account_reference` | The PAR of the card, see below. Returned when scheme references are enabled for your account. | Filter the token list by the last four digits of the TPAN the same way you filter by the card number: ```bash theme={null} curl --request GET \ --url 'https://api.hellgate.io/tokens?network_token_last_four=6777' \ --header 'x-api-key: ' ``` The value is four characters; anything else is rejected with a validation error. The filter only matches tokens whose digits are already stored. All three fields are also part of the `token.created` and `token.updated` webhook payloads, the PAR again only when scheme references are enabled. The network token is provisioned after the token is created, so `token.created` normally carries `null` digits; they arrive with a `token.updated` event. Only the masked digits leave the card data environment. The full TPAN stays PCI-scoped and is only available through [payment data](#network-token-payments). The digits are filled in as the card scheme reports them: at provisioning for newly created tokens, and for tokens provisioned earlier on their next payment-data request or through a backfill run by Hellgate. Every change is announced by a `token.updated` webhook. Until then the fields are `null` while `network_token_status` is already `active`. Visa reports the PAR only at provisioning, so Visa tokens provisioned earlier stay without one. When the network token is deleted, the digits and the PAR are cleared. ### Payment Account Reference The **Payment Account Reference (PAR)** is a scheme-issued identifier for the underlying card account. The card number (FPAN) and every network token derived from it share the same PAR, so it lets you recognize one card across its tokens and across channels without handling card data. It cannot be used to make a payment. ## Network token payments To use a network token for payment authorization, a cryptogram must first be requested. It is created and used during the payment request as proof of token validation for card transactions requiring authorization, helping to authenticate the transaction and ensure its integrity. The sequence diagram below shows how to use a network token and request a cryptogram. ```mermaid theme={null} sequenceDiagram autonumber participant M as Merchant participant C as Hellgate Commerce participant S as Card Scheme participant P as PSP M->>+C: /tokens/{id}/payment-data C->>+S: Generate Cryptogram S->>-C: Cryptogram C->>-M: Payment Data M->>+P: Payment Authorization with Network Token and Cryptogram P->>-M: Authorization Result ``` Both the network token (TPAN) and its cryptogram are PCI-scoped data. You can consume a payment-data bundle in two ways: * **Decrypt it yourself** — read `encrypted_authentication_data` from the response, decrypt it with your merchant key, and send the network token and cryptogram to your PSP. This requires you to handle PCI-scoped data. * **[Forward without handling card data](#forward-without-handling-card-data)** — let Commerce inject the network token, cryptogram, and related fields into your acquirer request server-side, so the raw values never reach your systems. In the **Managed Ecosystem** operating model, `encrypted_authentication_data` is not returned. Use [forwarding](#forward-without-handling-card-data) to authorize payments. ### Step 1: Request authentication data Commerce allows you to create these cryptograms using a previously imported token. ```bash theme={null} curl --request POST \ --url https://api.hellgate.io/tokens/{id}/payment-data \ --header 'content-type: application/json' \ --data '{ "amount": 10000, "currency_code": "EUR", "merchant_id": "afb55b3a-9997-4ddf-8026-a178fd8573cc", "reference": "1234567890" }' ``` ### Step 2: Decrypt authentication data Commerce processes the request and returns the encrypted authentication data. This encrypted authentication data contains the network token and the cryptogram. For more information, please refer to our [API documentation](/products/commerce/v2/api-reference/getting-started). The network token is used to authenticate the transaction, and the cryptogram is used to validate the network token. To decrypt the payload, follow these steps: * Use the `encryption_key` that was generated when the merchant was created * Use the `encrypted_authentication_data` string, which you receive as part of requesting a cryptogram response Below is an example of how to perform decryption in Elixir. However, this can also be accomplished using your preferred programming language. ```elixir theme={null} merchant_key = "e4xeJFoL+8kQWrSwCyJLnC56WkGz8aGIE8uEoQJGYDs" encrypted_authentication_data = "eyJhbGciOiJBMjU2R0NNS1ciLCJlbmMiOiJBMjU2R0NNIiwiaXYiOiJpR2ZsaXRMajJxLXpaMkdHIiwidGFnIjoia1c2bWRTQkJJcHBLMWwzQW93ZFBFUSJ9..." JOSE.JWE.block_decrypt( :sha256 |> :crypto.hash(merchant_key) |> JOSE.JWK.from_oct(), encrypted_authentication_data ) |> elem(0) |> Jason.decode! ``` The decrypted payload will be in JSON format and will appear as follows: ```elixir theme={null} { "cryptogram" => "AwAAAAkA+1rBeAMAnkbHgpAAAAA=", "eci" => "07", "network_token" => %{ "expiry_month" => 3, "expiry_year" => 2030, "token" => "4895370017908176" } } ``` ### Step 3: Authorize with your PSP ```bash theme={null} curl 'https://api.hellgate.io/forward' \ -X 'POST' \ -H 'Content-Type: application/json' \ -H 'X-API-KEY: ' \ -H 'X-HELLGATE-TOKEN: d7d272f7-f0e8-44c6-9459-c60e1ef279d5' \ -H 'X-DESTINATION-URL: https://checkout-test.adyen.com/v69/payments' \ -H 'X-API-KEY-ADYEN-NAME: X-API-KEY' \ -H 'X-API-KEY-ADYEN-VALUE: ' \ --data '{ "merchantAccount": "", "reference": "Payment-0001", "amount": { "currency": "EUR", "value": 1234 }, "paymentMethod": { "type": "networkToken", "brand": "visa", "expiryMonth": "3", "expiryYear": "2030", "number": "4895370017908176" }, "mpiData": { "directoryResponse": "Y", "authenticationResponse": "Y", "tokenAuthenticationVerificationValue": "AwAAAAkA+1rBeAMAnkbHgpAAAAA=", "eci": "07" }, "recurringProcessingModel": "CardOnFile", "shopperInteraction": "Ecommerce" }' ``` ## Forward without handling card data If you would rather not decrypt and handle the network token and cryptogram yourself, forward your acquirer request through [`POST /payment-data/{id}/forward`](/products/commerce/v2/api-reference/network_tokens/forward-payment-data). Commerce forwards the request to the destination URL configured in your merchant settings and injects the PCI-scoped values via placeholders, so the raw network token and cryptogram never touch your systems. This is the only way to authorize payments in the **Managed Ecosystem** operating model. Reference each value with a placeholder in your request body: | Placeholder | Description | | - | - | | `{{ network_token }}` | Network token number (TPAN). | | `{{ cryptogram }}` | TAVV cryptogram. | | `{{ dynamic_cvv }}` | Dynamic CVV, when present instead of a TAVV. | | `{{ eci }}` | Electronic Commerce Indicator. | | `{{ cardholder_name }}` | Cardholder name. | | `{{ expiration_month }}` | Two-digit expiry month. | | `{{ expiration_year }}` | Four-digit expiry year. | | `{{ amount }}` | Authenticated amount in minor units. | | `{{ currency_code }}` | Currency code of the authenticated amount. | Number placeholders are strings by default; append `| unwrap` to emit the native type, for example `{{ expiration_year | unwrap }}`. See [Forward payment-data](/products/commerce/v2/api-reference/network_tokens/forward-payment-data) for the full reference. Under the hood this builds on [Guardian's cryptogram forwarding](/products/guardian/tokens/network-tokens#forwarding-cryptograms). # Overview Source: https://developer.hellgate.io/products/commerce/v2/tokens/overview Tokenize and manage cardholder data with Commerce. We are a PCI DSS Level 1 certified service provider. Find the [Attestation of Compliance (AoC)](https://trustcenter.hellgate.io) in our trust-center. Tokenize cardholder data with the Commerce Web SDK in a PCI DSS-compliant way. Use network tokens from card schemes with Commerce. Forward token data to your payment service provider securely. ## Built on Guardian Commerce stores all tokens in [Guardian](/products/guardian/overview) — the Hellgate PCI-compliant tokenization vault. Token storage, network token provisioning, and lifecycle management are all handled for you through a Guardian instance that Hellgate manages on your behalf. If you already operate a Guardian instance, you can connect it to your Commerce account instead (**Bring Your Own Guardian**). Your existing tokens work directly in Commerce without re-tokenizing, and card data stays in the instance you control. BYOG is configured at the account level — contact our support to set it up. ## Easy compliance In general you have to handle card payments in a PCI DSS compliant way. This means there is no way around it, but you can make it much easier if you follow this general advice: 1. Make use of managed solutions like our [Web SDK](/platform/resources/sdks/web-sdk/overview) such that you can accept card payments without having to touch the card data. 2. Ensure Transport Layer Security (TLS) for all payment pages. 3. Assess your PCI compliance yearly using a Self-Assessment Questionnaire (SAQ) which are provided by the [PCI Security Standards Council](https://www.pcisecuritystandards.org/). We offer two ways to make PCI compliance easy for you: ### SAQ A compliance Our SDKs are designed to be used in a way that you can achieve SAQ A compliance. This is the easiest way to be PCI DSS compliant. You can use our SDKs to accept card payments without having to touch the card data. We handle the sensitive data and exchange it for a non-sensitive token, which you can safely store and use to charge the card. ### SAQ D compliance If you are SAQ D PCI DSS compliant, you can also send us cardholder data in raw format. Still, you don't have to worry about storing the cardholder information and we will provide you with the same convenient token in exchange. ## Commerce token features In contrast to many simple tokens, Commerce tokens are not just a non-sensitive replacement but offer a rich feature-set themselves. ### Card metadata For certain use cases, understanding more about the card itself or the issuing bank can be crucial. Commerce tokens are enriched with comprehensive metadata that provides insights such as whether the card is a consumer or commercial type, along with other relevant attributes. This information enables more informed decision-making and helps tailor processing strategies based on card characteristics. For a detailed list of available metadata and usage examples, please consult our [API Reference](/products/commerce/v2/api-reference/tokens/overview). ### Network tokens Many major card schemes, such as Visa and Mastercard, offer the ability to tokenize cardholder data using tokens provisioned directly by the network. These network tokens not only enhance security but also improve transaction approval rates and are essential for enabling device-based payment experiences, including mobile wallets and in-app payments. When your account has network token provisioning enabled, the tokens are automatically requested and managed whenever a new Commerce token is issued. This process ensures seamless integration with the card schemes and reduces the complexity of managing token lifecycles manually. Reach out to your Commerce support representative to learn more about network tokens and how to make use of them. ### Account updates Especially for the use in business models with recurring payments, expired or replaced card information often generates unnecessary friction. Commerce supports an automatic account update process with major card schemes, to replace the underlying card data automatically or upon request. Reach out to your Commerce support representative to learn more about account updates and how to make use of them. ## Tokenization Tokenization is the process of converting the sensitive cardholder data into a non-sensitive Commerce token. This enables PCI DSS-compliant processing and a high level of security for your card payments. In order to explain how tokenization works, we need to distinguish between SAQ A and SAQ D compliance levels (see [PCI Compliance](https://www.pcisecuritystandards.org/)). ### For SAQ A compliance The majority of our customers fall into this category. If you are SAQ A compliant, you can use our SDKs to tokenize cardholder data. The process of using the SDKs is, in general, identical across all SDKs we provide. It consists of these steps: 1. **Server Side**: Session Creation 2. **Client Side**: Session Initialization 3. **Client Side**: Tokenization Along the lifecycle of the tokens, you can subscribe to notifications to stay informed about token changes. Find our get started guide to learn how to [tokenize cardholder data](/products/commerce/v2/tokens/getting-started) using our SDKs. ### For SAQ D compliance If you are SAQ D compliant, instead of using our SDKs, you can also send us cardholder data in raw format on the API. Commerce will, in this case, exchange the cardholder information directly into a Commerce token. For further details, consult our API reference to learn how to [create token from FPAN](/products/commerce/v2/api-reference/tokens_create/create-token-from-fpan). ```bash theme={null} curl --request POST \ --url https://sandbox.hellgate.io/cde-import \ --header 'content-type: application/json' \ --data '{ "cardholder_name": "John Doe", "expiry_month": 4, "expiry_year": 2033, "account_number": "4242424242424242", "security_code": "123" }' ``` # Wallet Implementation Source: https://developer.hellgate.io/products/commerce/v2/wallets/implementation Implement your branded in-car wallet with Commerce. This document gets you started implementing your branded in-car wallet solution based on Commerce technology. If you haven't done so, please refer to the [In-Car Payments](/products/commerce/v2/wallets/in-car-payments) for an in-depth overview and instructions on how to set up your account. ## Overview To have the full experience, we will need a few components: * Somewhere where your users will manage their payment methods. This is for example your driver payment profile * A consumer application, leveraging the Wallet Consumer SDK. These are the apps collecting payments through your wallet * A wallet application, leveraging the In-Car Wallet SDK. This is your branded wallet. Our focus here is the third bullet, but we will hook you up with the first two. ### Your driver cards The purpose of this document is not to teach you how to manage your users' cards on file. For this purpose, we ship a small Docker image that lets you simulate that experience and get your cards into Commerce. You can always dive deeper into tokenization by referring to the [Commerce Tokens](/products/commerce/v2/tokens/overview) documentation. Make sure you have [Docker installed](https://docs.docker.com/engine/install/) and running in your system. Create a file somewhere safe in your file system, named `hellgate.env`, with the following content: ```bash theme={null} HELLGATE_API_KEY=hg_sndbx_XXX HELLGATE_BACKEND=https://sandbox.hellgate.io ``` Replace `hg_sndbx_XXX` with your actual Commerce API key. Now run the image with: ```bash theme={null} docker run --rm --env-file path/to/your/hellgate.env -p 4711:4711 hgate2sdk.azurecr.io/hellgate-tokenization-app:latest ``` If everything is running correct you should now be able to use the card management interface, by going to [http://localhost:4711](http://localhost:4711). You should be able to see all the cards in your account, if you have any, remove or add more cards, including going through the 3DS authentication process. Please refer to our [Test Card Data](/platform/resources/testing/test-card) page to view available test cards. Card management interface This app leverages the Commerce Web SDK and the [Tokens Management API](/products/commerce/v2/api-reference/tokens/overview) and is connected with your Commerce account. In a real-life scenario, a similar interface would only manage a specific user's cards. ### The consumer application The consumer application is used by your users to buy products in their car. Here they can search for products, browse through them and add them to their shopping basket. To get started this can be as simple as a single button to trigger a predefined payment process. Such a simple app is described in detail in the [In-Car Payments / Your app](/products/commerce/v2/wallets/in-car-payments) documentation. As you will receive the wallet-template archive you will be able to use it as a base for your consumer application. It is part of the android project and can be found in the `simpleshop` folder, to get you started right away. ### The wallet application The wallet application is the one that will be used by your users to manage their payment methods, create device bindings and view their transactions. In the wallet-template package we provide you with a simple implementation of the wallet application. #### Setup variables In the `MyApplication.kt` file there are multiple constants that should be set according to your Commerce account. The baseUrl can stay untouched for now as we are aiming to run against the sandbox environment. The API key should be set according to your Commerce account / wallet account. Device id and customer id should be set to unique values for your testing, in a production environment the device id need to be identical with the VIN of the car. The customer id also needs to unique in the context of your wallet account, so make sure to use a different one for each customer. The customer email and name can be set to artificial values for the sandbox testing but needs to be provided correctly in a production environment. The customer name should also be aligned with the cardholder name that you used for tokenization since the `PaymentMethodClient` will filter payment methods that are displayed in the app according to the customer name provided. If you are not seeing any payment methods in the wallet app, please check the customer name you provided in the `MyApplication.kt` file. ```kotlin theme={null} const val BASE_URL = "https://in-car-wallet.fly.dev" const val API_KEY = "wallet-api-key" const val DEVICE_ID = "756f65d7d1464719abe9fbdf" const val CUSTOMER_ID = "customer-tester2-001" const val CUSTOMER_EMAIL = "tester2@hellgate.io" const val CUSTOMER_NAME = "Tester2" class MyApplication : Application() { ... } ``` # In-Car Payments Source: https://developer.hellgate.io/products/commerce/v2/wallets/in-car-payments Build in-car commerce and wallet experiences with Commerce SDKs. Connected commerce around cars is a growing business area. The car is becoming a platform for services and applications, which are not only related to driving. The car is becoming a hub for services, which are related to the driver and passengers. This includes services like entertainment, navigation, and also payments. The car is becoming a wallet on wheels. Starfish provides Software Development Kits (SDK) which turn cars into mobile wallets and enable providers of in-car commerce experiences, with a convenient approach to implement their solutions. ## High-level overview In order to understand the integration of an In-Car Wallet, it is important to understand the context in which it is used. The following illustration shows the main building blocks and their primary interactions. High-level wallet overview ### Car context The car context revolves around the In-Car Wallet and the In-Car Applications that utilize it. Starfish provides two SDKs to simplify the development of these components. #### In-Car Application In-Car Applications enable commerce-related use cases such as parking, fueling, and food delivery. Typically offered by third-party providers, these apps are independent of the car manufacturer’s core offerings. To streamline implementation and ensure compatibility across providers, Starfish offers the Wallet Consumer SDK. This lightweight library provides a simple interface for integrating with the In-Car Wallet. #### In-Car Wallet The In-Car Wallet is a core component provided by car manufacturers. It securely stores payment methods, manages transactions, and offers a user interface for wallet interactions. While the wallet experience is tailored to the car manufacturer’s brand, Starfish’s Wallet SDK abstracts payment processing, enabling rapid development of fully certified in-car payment solutions. ### Backends Supporting the car context are backend services that handle business logic and third-party integrations. #### Application backends These services power In-Car Applications by implementing business logic and managing connections to third-party services, such as payment gateways and providers. It is important to understand that the Wallet itself only handles the authentication of the shopper for a payment. The actual payment processing is done by the application backend. #### Wallet Service The Wallet Service, provided by Starfish, manages wallet assets and interfaces with the payment ecosystem to ensure secure, seamless transactions. ### Payment tokens During a transaction, the In-Car Application prompts the driver or passenger to authenticate themselves to authorize the payment. Upon successful authentication, the In-Car Wallet generates a payment token. This token can be used by the app provider with their payment processing system to request the required authorization. ## Getting started with app development For the sake of simplicity, we are not focusing here on the implementation of In-Car Wallets, but on their use for In-Car Applications. If you want to dive deeper into the implementation of the Wallet, please reach out to your account representative. ### Prerequisites We need a couple of extra things but they are easy to get; then we move focus to the app. #### Your Commerce account First, go to [Hellgate.io](https://hellgate.io/in-car-payment) and register for a sandbox account if you don't have one yet. Once you're in, you can head over to the [Wallet Developer Settings](https://app.hellgate.io/settings/wallet) section on the left menu bar and you should see your wallet credentials to get access to the app dependencies. It might take a second since the credentials are created for you just in time. Keep them around, you will need them for later, or come back to the page if you need them again. #### The Wallet Simulator Cars are notoriously hard to keep around in the office, so they don't make good test instruments. Because of this we have developed a wallet simulator that works just like your car wallet would, with some extra functionality for testing purposes, and the obvious branding difference. It is Commerce wallet behind the scenes, so you can develop with confidence. To Install the simulator: * If you have a test device, head to the [Simulator Play Store page](https://play.google.com/apps/testing/io.hellgate.hw.walletsimulator) and install it as usual. * If you are using the emulator, contact your account representative or reach out to our sales team via [hellgate.io/demo](https://hellgate.io/demo) to obtain the latest APK, then install it by dragging the APK file into your running emulator screen. Once you have it installed, open the app and follow the quick instructions to configure it with your Commerce wallet account ID that you got in the previous step. Ignore the webhook field for the moment. And that is it, close the app, there is no save button, and you are ready to go. Now it's time to move over to your own app and start a payment. We will follow up with some more screenshots of the simulator in action, to give you an idea what to expect. Configure the simulator When you start a payment from your app you will be prompted with an outcome; this is how you simulate the various possibilities, from a successful payment to an authentication failure. Simulator: select a card Simulator: authentication Simulator: outcome ### Your app Finally we get to do some code, but not a whole lot. If you have a running app, choose where you want to place your "pay now" button and follow along. If you do not have an app, you can start a template project from Android Studio. Go to **File → New → New Project** and select a template; the empty activity one is a good place to start. Android Studio: pick a template #### Setting up the SDK dependency There are several ways to declare an additional Maven repository and dependencies; here is one via `settings.gradle.kts` and `app/build.gradle.kts`. Grab the Maven credentials from your Commerce dashboard and include them in your Gradle files. ```kotlin theme={null} // in settings.gradle.kts dependencyResolutionManagement { repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS) repositories { google() mavenCentral() maven { url = URI.create("https://maven.hellgate.io/repository/sf-maven") credentials { username = "QUP6VG965A" // username from your dashboard password = "************" // password from your dashboard } } } } // in app/build.gradle.kts dependencies { implementation("io.hellgate:hw.wallet-consumer-sdk:1.0.0") } ``` #### The payment authentication code Payments are initiated using everyday [Android intents](https://developer.android.com/guide/components/intents-filters). We fill a `io.hellgate.hw.consumer.PaymentData` object and using standard intent launch plumbing we launch the intent. In essence, this is the whole function we need to implement: ```kotlin theme={null} fun makePayment( scope: CoroutineScope, launcher: ManagedActivityResultLauncher, ) { scope.launch { launcher.launch( PaymentData( merchantRefId = "1234", signature = "TEST", amount = 10000, currencyCode = "USD", creditor = "Simple Store", subject = "Simple payment", ), ) } } ``` Now we also need to do something with the result. Our intent result is a `io.hellgate.hw.consumer.PaymentAuthenticationResult`, which can either be `Success` or `Failure`, and in case of a success, give us our payment token. Again, this does not mean the payment was completed, it just means it was authenticated, the actual payment should be done in your backend, but we will get there in a moment. For now lets just add a quick function to handle our result, in this case we will just print it out: ```kotlin theme={null} fun receiveResult(result: PaymentAuthenticationResult) { when (result) { is PaymentAuthenticationResult.Success -> Log.d("MainActivity", "Payment authentication successful, got the token: ${result.paymentToken}") is PaymentAuthenticationResult.Failure -> Log.d("MainActivity", "Received authentication failed") } } ``` #### The UI code From here on, it's all standard Android development. Let's create a simple button that calls our `makePayment` function: ```kotlin theme={null} @Composable fun PaymentButton( scope: CoroutineScope, launcher: ManagedActivityResultLauncher,) { Scaffold(modifier = Modifier.fillMaxSize()) { innerPadding -> Column( modifier = Modifier .padding(innerPadding) .fillMaxSize(), verticalArrangement = Arrangement.Center, horizontalAlignment = Alignment.CenterHorizontally, ) { Button(onClick = { makePayment(scope, launcher) }) { Text("Make a Payment") } } }} ``` The only relevant piece is the `onClick` handler invoking our `makePayment` function: ```kotlin theme={null} Button(onClick = { makePayment(scope, launcher) }) ``` So now all that is left is to add our button to the content of some activity: ```kotlin theme={null} setContent { val scope = rememberCoroutineScope() val launcher = rememberLauncherForActivityResult(PaymentIntentContract) { result -> receiveResult(result) } MyApplicationTheme { PaymentButton(scope, launcher) } } ``` The important pieces here are the intent launcher initialization with our receiver function: ```kotlin theme={null} val launcher = rememberLauncherForActivityResult(PaymentIntentContract) { result -> receiveResult(result) } ``` And displaying our button: ```kotlin theme={null} PaymentButton(scope, launcher) ``` #### Putting it all together If you started with the empty activity Android Studio template, the end result could look something like the code listing below. Of course in your own application, or if you used a different starter template, it will look different, but the important thing here is the pattern. ```kotlin theme={null} package team.starfish.myapplication import android.os.Bundle import android.util.Log import androidx.activity.ComponentActivity import androidx.activity.compose.ManagedActivityResultLauncher import androidx.activity.compose.rememberLauncherForActivityResult import androidx.activity.compose.setContent import androidx.activity.enableEdgeToEdge import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.padding import androidx.compose.material3.Button import androidx.compose.material3.Scaffold import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import io.hellgate.hw.consumer.PaymentAuthenticationResult import io.hellgate.hw.consumer.PaymentData import io.hellgate.hw.consumer.PaymentIntentContract import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.launch import team.starfish.myapplication.ui.theme.MyApplicationTheme class MainActivity : ComponentActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) enableEdgeToEdge() setContent { val scope = rememberCoroutineScope() val launcher = rememberLauncherForActivityResult(PaymentIntentContract) { result -> receiveResult(result) } MyApplicationTheme { PaymentButton(scope, launcher) } } } } @Composable fun PaymentButton( scope: CoroutineScope, launcher: ManagedActivityResultLauncher, ) { Scaffold(modifier = Modifier.fillMaxSize()) { innerPadding -> Column( modifier = Modifier .padding(innerPadding) .fillMaxSize(), verticalArrangement = Arrangement.Center, horizontalAlignment = Alignment.CenterHorizontally, ) { Button(onClick = { makePayment(scope, launcher) }) { Text("Make a Payment") } } } } fun makePayment( scope: CoroutineScope, launcher: ManagedActivityResultLauncher, ) { scope.launch { launcher.launch( PaymentData( merchantRefId = "1234", signature = "TEST", amount = 10000, currencyCode = "USD", creditor = "Simple Store", subject = "Simple payment", ), ) } } fun receiveResult(result: PaymentAuthenticationResult) { when (result) { is PaymentAuthenticationResult.Success -> Log.d("MainActivity", "Payment authentication successful, got the token: ${result.paymentToken}") is PaymentAuthenticationResult.Failure -> Log.d("MainActivity", "Received authentication failed") } } ``` You should now be able to simulate a payment authorization and get the token printed out. If you started from the Android Studio template, this is how the app should look: Sample app #### More on PaymentData The `PaymentData` data class contains the minimum information required to authenticate the payment. It is all you need to start a payment, plus a few extra properties to assist the actual wallet app with the information display. ```kotlin theme={null} data class PaymentData( val merchantRefId: String, // unique transaction id, will not be displayed to the user val signature: String, // for simulation use "TEST" val amount: Int, // amount in minor currency unit val currencyCode: String, // ISO 4217 val creditor: String, // display only; in production verified with signature val subject: String, // description of the payment ) ``` #### Doing something with the result Right now we are just printing out our result. But what we really want is to make a payment, and there are two possible courses of action: * You receive the payment token in your app and you send it back to your own backend for further processing * Preferred: you configure your account with a webhook that receives the payment token directly from Commerce, and you fetch the status from your frontend When you created your Commerce account we also configured a simple backend simulator for you, which basically receives the token and simulates a payment. You can then use said backend server to fetch the payment status and update your app UI and behavior according to the new purchase that was just made. So this is what we will use in this guide. Because polling for a result is such a common action, the Wallet Consumer SDK provides a helper function named `simplePolling` that takes a URL and polls until it receives a successful result. We will leverage that function, punch in our backend simulator URL, and that will get us a result that honours what we selected as the outcome in the wallet simulator. We then replace the `Success` case in our `receiveResult` function with our polling function: ```kotlin theme={null} simplePolling("https://icp-wallet-backendsimulator.fly.dev/merchant/$merchantId/payment/${result.paymentId}") .onSuccess { Log.d("MainActivity", "Received payment status: $it") } ``` The whole thing, with a few extra logging statements and making our function suspend, now looks like this: ```kotlin theme={null} suspend fun receiveResult(result: PaymentAuthenticationResult) { Log.d("MainActivity", "Received result: $result") val merchantId = "hardwareStore123" when (result) { is PaymentAuthenticationResult.Success -> { Log.d("MainActivity", "Authentication: SUCCESS, Payment status: PENDING") Log.d("MainActivity", "Fetching payment status from merchant server") simplePolling("https://icp-wallet-backendsimulator.fly.dev/merchant/$merchantId/payment/${result.paymentId}") .onSuccess { Log.d("MainActivity", "Received payment status: $it") } } is PaymentAuthenticationResult.Failure -> Log.d("MainActivity", "Received authentication failed") } } ``` This particular piece requires internet access. If you are trying this out in your existing app it is very likely that permission is already set, but if you started with the Android Studio template, don't forget to add it to `AndroidManifest.xml`: ```xml theme={null} ``` One final piece: we made our `receiveResult` suspend because we are going out to the internet. So we need to update our `launcher` configuration to send that result through our coroutine scope: ```kotlin theme={null} val launcher = rememberLauncherForActivityResult(PaymentIntentContract) { result -> scope.launch { receiveResult(result) } } ``` #### Final result With the recent changes, our demo code leveraging our simulators should look similar to this: ```kotlin theme={null} package team.starfish.myapplication import android.os.Bundle import android.util.Log import androidx.activity.ComponentActivity import androidx.activity.compose.ManagedActivityResultLauncher import androidx.activity.compose.rememberLauncherForActivityResult import androidx.activity.compose.setContent import androidx.activity.enableEdgeToEdge import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.padding import androidx.compose.material3.Button import androidx.compose.material3.Scaffold import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import io.hellgate.hw.consumer.PaymentAuthenticationResult import io.hellgate.hw.consumer.PaymentData import io.hellgate.hw.consumer.PaymentIntentContract import io.hellgate.hw.consumer.polling.simplePolling import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.launch import team.starfish.myapplication.ui.theme.MyApplicationTheme class MainActivity : ComponentActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) enableEdgeToEdge() setContent { val scope = rememberCoroutineScope() val launcher = rememberLauncherForActivityResult(PaymentIntentContract) { result -> scope.launch { receiveResult(result) } } MyApplicationTheme { PaymentButton(scope, launcher) } } } } @Composable fun PaymentButton( scope: CoroutineScope, launcher: ManagedActivityResultLauncher, ) { Scaffold(modifier = Modifier.fillMaxSize()) { innerPadding -> Column( modifier = Modifier .padding(innerPadding) .fillMaxSize(), verticalArrangement = Arrangement.Center, horizontalAlignment = Alignment.CenterHorizontally, ) { Button(onClick = { makePayment(scope, launcher) }) { Text("Make a Payment") } } } } fun makePayment( scope: CoroutineScope, launcher: ManagedActivityResultLauncher, ) { scope.launch { launcher.launch( PaymentData( merchantRefId = "1234", signature = "TEST", amount = 10000, currencyCode = "USD", creditor = "Simple Store", subject = "Simple payment", ), ) } } suspend fun receiveResult(result: PaymentAuthenticationResult) { Log.d("MainActivity", "Received result: $result") val merchantId = "hardwareStore123" when (result) { is PaymentAuthenticationResult.Success -> { Log.d("MainActivity", "Authentication: SUCCESS, Payment status: PENDING") Log.d("MainActivity", "Fetching payment status from merchant server") simplePolling("https://icp-wallet-backendsimulator.fly.dev/merchant/$merchantId/payment/${result.paymentId}") .onSuccess { Log.d("MainActivity", "Received payment status: $it") } } is PaymentAuthenticationResult.Failure -> Log.d("MainActivity", "Received authentication failed") } } ``` # Create blacklist value Source: https://developer.hellgate.io/products/specter/api-reference/blacklist/create-blacklist-value /products/specter/openapi.yaml post /api/admin/blacklist Adds a blacklist entry that blocks a value at a given `field_path`, optionally with a `ttl_seconds` expiry. The path must resolve to a scalar and must not be `pci-pan`- or `pci-sad`-scoped — array- or object-typed and regulated paths reject with `422`; blacklist cards by `$.credential_fingerprint`. Entries carry no schema binding, so the check is conservative across every registered schema and version. # Delete blacklist value Source: https://developer.hellgate.io/products/specter/api-reference/blacklist/delete-blacklist-value /products/specter/openapi.yaml delete /api/admin/blacklist/{id} Removes a blacklist entry by its ID. # Get blacklist value Source: https://developer.hellgate.io/products/specter/api-reference/blacklist/get-blacklist-value /products/specter/openapi.yaml get /api/admin/blacklist/{id} Retrieves a single blacklist entry by its ID. # Get blacklist values Source: https://developer.hellgate.io/products/specter/api-reference/blacklist/get-blacklist-values /products/specter/openapi.yaml get /api/admin/blacklist Lists blacklist entries. Supports pagination. # Create decision Source: https://developer.hellgate.io/products/specter/api-reference/decisions/create-decision /products/specter/openapi.yaml post /api/decisions Evaluate a payload and receive an `ALLOW`, `REVIEW`, or `BLOCK` decision. If an asynchronous backend rule is involved, the response is `PROVISIONAL` and updated later. `context` selects the active ruleset, and the ruleset's bound [schema](/products/specter/concepts/schemas) defines the payload. The body below is the built-in `transaction` schema; a context bound to a custom schema takes that schema's fields in the same envelope. The payload validates against the schema before any rule runs — shape, required fields, and each field's type rule — and a failure returns `422` naming the path without echoing regulated values. Below PCI level `SAQ_D`, a payload carrying `pci-pan` or `pci-sad` fields rejects with `422`. # Get decision details Source: https://developer.hellgate.io/products/specter/api-reference/decisions/get-decision-details /products/specter/openapi.yaml get /api/decisions/{id} Retrieve a previously evaluated decision by its ID. The entry includes the original decision, the rules that fired, backend results, the current payment lifecycle state, and any resolution. Payment Card details are masked and not returned in detail. # Get decision evidence Source: https://developer.hellgate.io/products/specter/api-reference/decisions/get-decision-evidence /products/specter/openapi.yaml get /api/decisions/{id}/evidence Retrieve, for every rule of the ruleset version pinned to the decision, its outcome and — for each rule that fired — the evidence: the resolved operands of a comparison, the counter value / window / threshold of a velocity rule, the entry a blacklist rule matched. The trace uses the same entry shape as [`simulate`](/products/specter/api-reference/rulesets/simulate-ruleset). Evidence is **replayed** where the input is frozen — the pinned ruleset re-evaluated against the stored payload, deterministically — and **captured** at decision time where it is not (velocity counters, blacklist matches). Each entry states `evidence_source` and `replay_fidelity`. Replay reproduces only what retention preserved: a field whose scope was not retained, or stored in reduced form, is absent to the replayer and the rule abstains on replay (`reduced`). HMAC outputs and the credential fingerprint do reproduce. Backend rules are never re-invoked. A `replayed_verdict` that differs from `decision` is therefore an expected artifact of partial retention, not an error; it is null whenever any entry is `reduced` or a backend rule participated. Availability follows the stored payload's opt-in retention: `200` with `retained: false` when nothing was kept, `404` for an unknown id. # Get decision payload Source: https://developer.hellgate.io/products/specter/api-reference/decisions/get-decision-payload /products/specter/openapi.yaml get /api/decisions/{id}/payload Retrieve the request payload as it stood when the decision was evaluated, redacted per classification marker at write time: `any` fields keep their value, `pii` fields keep their path with a `[REDACTED]` sentinel, `pci-pan` values are stored as the credential fingerprint, `pci-sad` is never stored. The store therefore holds no raw PAN or SAD and is available below `SAQ_D`. Storage is opt-in per scope and off by default. Your instance configures a retention window per scope (`any`, `pii`, `pci-pan`); the `pii` and `pci-pan` windows cannot exceed the instance's general retention window for that scope. Nothing stored is a normal state: the response is `200` with `retained: false`. `404` means the decision id is unknown. Storage is asynchronous, so a read immediately after the decision may briefly report `retained: false`. # Event notification Source: https://developer.hellgate.io/products/specter/api-reference/event-notification /products/specter/openapi.yaml webhook event_notification Sent by Specter to your registered webhook URL when a subscribed decision event occurs. Respond with a 2xx status to acknowledge receipt; non-2xx responses are retried. # API Information Source: https://developer.hellgate.io/products/specter/api-reference/getting-started Essential information for using the Specter API. This reference is essential for understanding and integrating with Specter. Specter is the decision engine of the Hellgate Cloud Platform, built for pre-authorization fraud decisions and for rules over any payload you declare. Every endpoint on this reference, generated from the same OpenAPI specification and grouped exactly as the navigation here. Duplicate the **Dedicated instance — template** environment and set `instance` and `env`. The Authorization tab is pre-configured for the OAuth2 client-credentials grant, so add `clientId`, `clientSecret`, `audiences` and `scopes` to the environment and hit **Get New Access Token**. Both `audiences` and `scopes` are space-delimited lists — name every instance the token may call. See [Authentication](/platform/authentication). Specter APIs make use of RESTful conventions where it makes sense. All calls use the standard HTTP verbs to express access semantics, like `GET`, `POST`, `PATCH`, and `DELETE`. Other related conventions are described below. ## JSON Conventions * Resources are addressable by a UUID `id` property. * Property names are always in `snake_case`. * Temporal data is encoded in [ISO 8601](https://en.wikipedia.org/wiki/ISO_8601) strings. * Monetary amounts are integers in the **minor units** of the transaction currency (e.g. `14999` = €149.99). * Currencies are [ISO 4217](https://en.wikipedia.org/wiki/ISO_4217) codes. ### Empty Strings and Omitted Fields The empty string is not a value. A string field is either **omitted** or present with a **real value** — and where a field is nullable, an explicit `null` unsets it. Never send `""`. Where a field is validated as a string, `""` is rejected with **422 Unprocessable Entity** and a field error on that path (see [Request Errors](#request-errors) for the response format). Not every field carries that validation, so do not treat the rejection as a safety net: where it is absent, `""` is accepted and then stored or forwarded verbatim — an empty string travels further into a request than a missing field ever does. In Specter, omission also shapes the evaluation itself. A field you omit — or send as `null` — resolves to **absent** in `$.`-path [rule conditions](/products/specter/concepts/rule-engine): every predicate over it **abstains**, `neq` and `not_in` included, so a "not equal" rule never fires on data you never sent. An empty string or empty array counts as present. Omission also changes derived values: a `masked_pan` credential sent without `expiry_month` and `expiry_year` yields a different `credential_fingerprint` than the same card sent with them, so velocity counts and blacklist entries keyed on `$.credential_fingerprint` do not line up across the two. ## Authentication Specter authenticates every request with a signed JSON Web Token (JWT) passed as a bearer token in the `Authorization` header. This OAuth2 bearer scheme is the standard across the Hellgate Cloud Platform services — see [Authentication](/platform/authentication). ```bash theme={null} curl --header 'Authorization: Bearer ' \ --request POST 'https://my-instance.eu1.on-hellgate.cloud/api/decisions' ``` Tokens carry **scopes** that gate access to individual endpoints, and are validated against your instance's audience on each request. See [Authentication](/products/specter/authentication) for the full scope reference. Tokens must be handled with care and kept secure. Never hardcode them in your source code — keep them solely on your backend systems. ## API Use ### Instance URL Specter is provided as a service of the Hellgate Cloud Platform implementing the [Composable Payment Architecture (CPA)](/platform/cpa). Each instance is accessible at a unique host: ``` https://{instance}.eu1.on-hellgate.cloud ``` `{instance}` is your unique instance slug and `eu1` is the current environment; both are provided during onboarding. ### Pagination Endpoints that return lists of objects support pagination. Specter uses cursor-based pagination with the following query parameters: | Parameter | Type | Description | | - | - | - | | `limit` | `integer` | The maximum number of objects returned per request. Default is 20. | | `after` | `string` | A pagination cursor. Pass the cursor from the previous page to fetch the next one. | Example request: ```bash theme={null} curl --header 'Authorization: Bearer ' \ 'https://my-instance.eu1.on-hellgate.cloud/api/admin/rulesets?limit=10&after=08f4b968-259a-4989-b5ab-09ef9414f983' ``` The response wraps the results in `data` and includes a `links` object for paging forward: ```json theme={null} { "data": [], "links": { "next": "https://my-instance.eu1.on-hellgate.cloud/api/admin/rulesets?limit=10&after=605d229d-fc8a-4017-b115-2e606031bd79" } } ``` When there are no further pages, `links.next` is `null`. ### Request Errors Specter uses standard HTTP status codes to indicate client errors on the API level. The response payload for processing errors follows a standard format. ```json theme={null} { "classifier": "NOT_FOUND", "code": 404, "message": "The requested resource does not exist" } ``` | Field | Description | | - | - | | `classifier` | A machine-readable classifier for the error. | | `code` | The HTTP status code, repeated in the body. | | `message` | A human-readable description. | Validation errors follow a standard format that includes the individual field errors. ```json theme={null} { "classifier": "VALIDATION_ERROR", "code": 422, "validation_errors": [ { "path": "limit", "message": "must be a positive integer" } ] } ``` Common classifiers: | Status | Classifier | When | | - | - | - | | `401` | `UNAUTHORIZED` | No valid authentication was provided. | | `403` | `FORBIDDEN` | The token lacks the scope required for the operation. | | `404` | `NOT_FOUND` | The requested resource does not exist. | | `409` | `CONFLICT` | The request conflicts with the current state (e.g. resolving an already-resolved decision). | | `422` | `VALIDATION_ERROR` | The request failed validation. | ### Security Considerations Specter evaluates sensitive payment data and requires strict security practices: * All communication must use HTTPS. * Bearer tokens must be stored securely and never exposed to clients. * The full PAN supplied in a decision request is never persisted. It is used for evaluation and fingerprinting and forwarded to the backends your rulesets invoke — see [what a backend receives](/products/specter/concepts/backends#what-a-backend-receives). ## Data Classification Fields that carry regulated data are marked with a classification pill next to the parameter name in this reference. The marker tells you what kind of data the field holds, so you can see at a glance which parts of a payload attract handling obligations before you write any code against them. | Marker | The field carries | | - | - | | pci-pan | The primary account number — a card number, or a value from which one can be reconstructed. | | pci-sad | Sensitive authentication data — security codes and the equivalent authentication values issued for a token. | | pii | Personal data under the GDPR — any information relating to an identified or identifiable person, not only the cardholder. | Most fields carry no marker at all. The absence of one is a statement that the field holds none of the categories above — not that the field has yet to be reviewed. Masked and truncated card numbers are the common example: `411111 •••••• 1111` identifies nobody on its own, so it carries no marker. The marker is also a gate, not just a label. Sending values into `pci-pan` or `pci-sad` fields requires your PCI DSS certification on file at **SAQ-D** or a full Report on Compliance (RoC). Sending [personal data](https://eur-lex.europa.eu/eli/reg/2016/679/oj) into `pii` fields requires a data processing agreement (DPA) between us. Contact [support@hellgate.io](mailto:support@hellgate.io) to put either in place or to learn more. # Create integration Source: https://developer.hellgate.io/products/specter/api-reference/integrations/create-integration /products/specter/openapi.yaml post /api/admin/integrations Register a Link instance as an integration so Specter can discover and use the backends it exposes. # Delete integration Source: https://developer.hellgate.io/products/specter/api-reference/integrations/delete-integration /products/specter/openapi.yaml delete /api/admin/integrations/{id} Remove a Link integration and the backends it exposed to your rulesets. # Get integration details Source: https://developer.hellgate.io/products/specter/api-reference/integrations/get-integration-details /products/specter/openapi.yaml get /api/admin/integrations/{id} Retrieve a single Link integration and the backends it exposes. # Get integrations Source: https://developer.hellgate.io/products/specter/api-reference/integrations/get-integrations /products/specter/openapi.yaml get /api/admin/integrations List the Link integrations registered on this instance. Each integration exposes one or more backends to your rulesets. # Update integration Source: https://developer.hellgate.io/products/specter/api-reference/integrations/update-integration /products/specter/openapi.yaml patch /api/admin/integrations/{id} Update a Link integration. # Activate interceptor Source: https://developer.hellgate.io/products/specter/api-reference/interceptors/activate-interceptor /products/specter/openapi.yaml post /api/admin/interceptors/{id}/activate Transitions an interceptor from DRAFT → ACTIVE. Any previously ACTIVE interceptor for the same context is demoted to INACTIVE. # Create interceptor Source: https://developer.hellgate.io/products/specter/api-reference/interceptors/create-interceptor /products/specter/openapi.yaml post /api/admin/interceptors Creates an interceptor — a transparent proxy that maps an acquirer request into a decision and is invoked at runtime by its reference. # Delete interceptor Source: https://developer.hellgate.io/products/specter/api-reference/interceptors/delete-interceptor /products/specter/openapi.yaml delete /api/admin/interceptors/{id} Deletes an interceptor configuration by its ID. # Execute interceptor Source: https://developer.hellgate.io/products/specter/api-reference/interceptors/execute-interceptor /products/specter/openapi.yaml post /api/interceptors/{ref} Submits an acquirer request body to the interceptor identified by `ref`; Specter maps it to a decision and either forwards it (ALLOW) or returns a configured block or review response. # Get interceptor details Source: https://developer.hellgate.io/products/specter/api-reference/interceptors/get-interceptor-details /products/specter/openapi.yaml get /api/admin/interceptors/{id} Retrieves an interceptor configuration by its ID. # Get interceptors Source: https://developer.hellgate.io/products/specter/api-reference/interceptors/get-interceptors /products/specter/openapi.yaml get /api/admin/interceptors Lists configured interceptors. Supports pagination. # Rollback interceptor Source: https://developer.hellgate.io/products/specter/api-reference/interceptors/rollback-interceptor /products/specter/openapi.yaml post /api/admin/interceptors/{id}/rollback Promotes an INACTIVE interceptor back to ACTIVE, demoting the current ACTIVE to INACTIVE. # Update interceptor Source: https://developer.hellgate.io/products/specter/api-reference/interceptors/update-interceptor /products/specter/openapi.yaml patch /api/admin/interceptors/{id} Updates an interceptor's field mapping or response behavior. # Get event details Source: https://developer.hellgate.io/products/specter/api-reference/lifecycle-events/get-event-details /products/specter/openapi.yaml get /api/events/{id} Retrieve a persisted lifecycle event by its ID. The entry includes the event payload and the delivery status for each notified backend. # Ingest lifecycle event Source: https://developer.hellgate.io/products/specter/api-reference/lifecycle-events/ingest-lifecycle-event /products/specter/openapi.yaml post /api/events Report a payment lifecycle event for a prior decision so Specter can keep the case and your fraud backends up to date. Reference the decision via `decision_id`. Specter advances the decision's payment state, forwards the event to the backends that participated in that decision, and stores a log entry with each backend's delivery status. A backend that does not support the event type is reported as `skipped` rather than failing the request. Lifecycle events are transaction-schema machinery: an event that references a decision evaluated under a custom-schema binding is rejected with `422` — custom schemas do not gain lifecycle, event, or side-effect machinery in v1. # Resolve review Source: https://developer.hellgate.io/products/specter/api-reference/resolutions/resolve-review /products/specter/openapi.yaml post /api/decisions/{id}/resolve Close a `REVIEW` decision by accepting or rejecting it after manual review. Specter forwards your resolution to the fraud backends that participated in the original decision. Only `REVIEW` decisions can be resolved, and each decision can be resolved once — resolving an already-resolved decision returns `409 CONFLICT`. # Activate ruleset Source: https://developer.hellgate.io/products/specter/api-reference/rulesets/activate-ruleset /products/specter/openapi.yaml post /api/admin/rulesets/{id}/activate Transitions a ruleset from DRAFT → ACTIVE. Any previously ACTIVE ruleset for the same context is demoted to INACTIVE. Activation rejects with 422 when the ruleset's schema binding is deprecated. # Create ruleset Source: https://developer.hellgate.io/products/specter/api-reference/rulesets/create-ruleset /products/specter/openapi.yaml post /api/admin/rulesets Creates a new DRAFT ruleset. Version is assigned automatically. Lifecycle: DRAFT → ACTIVE. The ruleset binds exactly one (schema, version) at creation; rules validate against the bound schema — unresolved field paths reject with 422, backend rules are only accepted in rulesets bound to the built-in transaction schema, and bindings naming a deprecated or unknown schema version reject. # Get ruleset details Source: https://developer.hellgate.io/products/specter/api-reference/rulesets/get-ruleset-details /products/specter/openapi.yaml get /api/admin/rulesets/{id} Retrieves a ruleset with its rules and lifecycle state. # Get rulesets Source: https://developer.hellgate.io/products/specter/api-reference/rulesets/get-rulesets /products/specter/openapi.yaml get /api/admin/rulesets Lists rulesets across all states (DRAFT, ACTIVE, INACTIVE). Supports pagination. # Rollback ruleset Source: https://developer.hellgate.io/products/specter/api-reference/rulesets/rollback-ruleset /products/specter/openapi.yaml post /api/admin/rulesets/{id}/rollback Promotes an INACTIVE ruleset back to ACTIVE, demoting the current ACTIVE to INACTIVE. # Simulate ruleset Source: https://developer.hellgate.io/products/specter/api-reference/rulesets/simulate-ruleset /products/specter/openapi.yaml post /api/admin/rulesets/{id}/simulate Evaluates a caller-supplied test payload against the persisted ruleset addressed by {id} — any lifecycle state (DRAFT / ACTIVE / INACTIVE) — and returns an evaluation trace. The ruleset's own (schema, version) binding validates the payload and resolves rule field paths; the request carries no schema selector and no context. Requires the dedicated admin:rulesets:simulate scope. The simulation is inert — no decision record, no velocity counter increment, no blacklist push, no webhook, no lifecycle event, no backend invocation. Velocity counters are read live and never written, so two identical simulations may differ; the trace labels counter values as live-read. When the request sets counter_store_unavailable, no counter is consulted at all — velocity rules resolve per their fail_posture instead. # Update ruleset Source: https://developer.hellgate.io/products/specter/api-reference/rulesets/update-ruleset /products/specter/openapi.yaml patch /api/admin/rulesets/{id} Updates `description` and/or `rules` on a DRAFT ruleset. Only DRAFT rulesets are editable. context and schema are identity fields fixed at creation — submitting a changed value rejects with 422 naming the field; resubmitting the current value unchanged is a no-op. Rebinding a context is a new ruleset, not an update. # Deprecate version Source: https://developer.hellgate.io/products/specter/api-reference/schemas/deprecate-version /products/specter/openapi.yaml post /api/admin/schemas/{name}/versions/{version}/deprecate Marks a version deprecated — a non-destructive marker. Requires the admin:schemas:write scope. Rulesets bound to the version keep evaluating and their traffic is accepted while an operational signal flags it; creating a ruleset bound to a deprecated version rejects, and activating a ruleset whose binding became deprecated rejects. Versions are never auto-removed; there is no hard-removal flow. Deprecating a built-in schema or an already-deprecated version is an invalid transition. # Get schema Source: https://developer.hellgate.io/products/specter/api-reference/schemas/get-schema /products/specter/openapi.yaml get /api/admin/schemas/{name} Returns registry metadata for one schema. Requires the admin:schemas:read scope. # Get version Source: https://developer.hellgate.io/products/specter/api-reference/schemas/get-version /products/specter/openapi.yaml get /api/admin/schemas/{name}/versions/{version} Returns one schema version, including its JSON Schema document. Requires the admin:schemas:read scope. # List schemas Source: https://developer.hellgate.io/products/specter/api-reference/schemas/list-schemas /products/specter/openapi.yaml get /api/admin/schemas Lists registry entries — built-in schemas (read-only) and custom schemas. Requires the admin:schemas:read scope. # List versions Source: https://developer.hellgate.io/products/specter/api-reference/schemas/list-versions /products/specter/openapi.yaml get /api/admin/schemas/{name}/versions Lists versions of one schema, newest first. Requires the admin:schemas:read scope. # Publish version Source: https://developer.hellgate.io/products/specter/api-reference/schemas/publish-version /products/specter/openapi.yaml post /api/admin/schemas/{name}/versions Publishes a new version of an existing custom schema. Requires the admin:schemas:write scope. Versions are system-assigned integers; every publication creates a new, immutable version, even for an identical document. Built-in schemas cannot be published to. Validation and rate limit match registration. A published version can be bound by a ruleset as soon as the response returns. # Register schema Source: https://developer.hellgate.io/products/specter/api-reference/schemas/register-schema /products/specter/openapi.yaml post /api/admin/schemas Registers a custom schema and publishes its version 1. The document must be a valid JSON Schema with known `x-specter-type` names and markers; internal `$ref` must resolve, external and recursive `$ref` are rejected, and caps on size, field count, nesting depth, and `$ref` count apply. Built-in and reserved names are rejected. Registrations and publications share a per-instance rate limit over a sliding hour; beyond it, `429` with `Retry-After`. # Delete webhook Source: https://developer.hellgate.io/products/specter/api-reference/webhooks/delete-webhook /products/specter/openapi.yaml delete /api/admin/webhooks/{id} Delete a webhook subscription. Specter stops delivering events to it. # Get webhook details Source: https://developer.hellgate.io/products/specter/api-reference/webhooks/get-webhook-details /products/specter/openapi.yaml get /api/admin/webhooks/{id} Retrieve a single webhook subscription. # Get webhooks Source: https://developer.hellgate.io/products/specter/api-reference/webhooks/get-webhooks /products/specter/openapi.yaml get /api/admin/webhooks List all webhooks known to the system # Register webhook Source: https://developer.hellgate.io/products/specter/api-reference/webhooks/register-webhook /products/specter/openapi.yaml post /api/admin/webhooks Register a webhook endpoint to receive decision event notifications. The `hmac_key` is generated by Specter and returned once, in the creation response. # Update webhook Source: https://developer.hellgate.io/products/specter/api-reference/webhooks/update-webhook /products/specter/openapi.yaml patch /api/admin/webhooks/{id} Update a webhook subscription's URL or its subscribed events. # Specter Updates Source: https://developer.hellgate.io/products/specter/changelog New releases and improvements of Specter. ## Improvements and bugfixes * Events, admin writes, and GraphQL requests that carry a card number are rejected with a `422` on every instance, SAQ-D included. This applies under any key, to card numbers sent as unquoted JSON numbers, and to card numbers in the query string. Simulation on a SAQ-D instance still accepts a card number in the designated credential field. ## Calendar-Aligned Velocity Windows Velocity rules now count over calendar periods, not just rolling windows: the condition's new `window_type` accepts `day`, `week`, `month`, `quarter`, and `year` alongside `rolling_seconds`. Boundaries are UTC — ISO-8601 weeks, Gregorian quarters and years — and every period instance carries a stable key (`"2026-W22"`, `"2026-05"`, `"2026-Q2"`) that the simulation trace reports. Existing rules are untouched: the legacy velocity shape reads back as `aggregate: count` over `rolling_seconds`. ## Amount-Sum Velocity Limits "Weekly amount limit €200" is now a single rule: `aggregate: sum` accumulates an amount-typed schema field per partition and window, in minor units, in a signed 64-bit accumulator. Counters auto-partition by the amount's ISO currency code, so a EUR limit never mixes with USD traffic and no FX conversion is involved. Sums move only on the decision path — lifecycle events such as refunds never adjust a counter — and the simulation trace shows which currency counter a test payload lands on. Rolling sums over `rolling_seconds` stay exact to the second up to 48 hours; longer rolling horizons use the new day-quantized `window_type: rolling_days` (`window_days` 2 to 366), which also serves `count`. ## Identity-Fingerprint Velocity A velocity counter can now be partitioned by an HMAC composition: an ordered list of filtered field references whose fingerprint becomes the counter key, so "max N accounts with identical PII" needs no pre-declared derived field. Canonicalization is the rule author's explicit choice via filter pipes; Specter applies none of its own — a PII string input without a normalizing filter is accepted and recorded in the audit trail as a weak-canonicalization warning. Rules share a fingerprint counter exactly when their compositions normalize identically. Compositions are limited to short windows (`rolling_seconds` up to 48 hours, or `day` / `week` / `month`) so that fingerprint-keyed state never outlives a calendar month. A `pci-pan` field may now partition a velocity counter, or be its `distinct_on` value — it is stored as its fingerprint, never raw; `pci-sad` stays rejected anywhere in a velocity condition. ## Cross-Customer Cardinality `aggregate: distinct_count` counts the unique values of a `distinct_on` attribute per partition — exactly, never approximately — enabling rules like "this payment method in at most 4 customer accounts". Under `bounded_long` and `rolling_seconds` each distinct value's membership expires a window-length after its first sighting; a calendar window resets its member set at the period boundary. The member set stays opaque: every surface reports the count, never the members. The new `bounded_long` window carries the member lifetime — one year by default, per-rule configurable from a day to five years. ## Choose How Velocity Rules Fail Every velocity rule now declares a `fail_posture` for counter-store failures: `open` (the default) abstains and lets evaluation continue — the previous behavior — while `closed` fires the rule's declared action. Postures resolve per rule, independently, even on a shared counter. A never-written counter is not a failure: it reads zero. Pass `counter_store_unavailable` to the simulation endpoint to preview posture outcomes before activating a ruleset. ## Every Provisional Decision Reaches a Final Answer A decision that consults an asynchronous backend comes back `PROVISIONAL`, and the final outcome follows on your webhook once the evaluation completes. If Specter restarted while that evaluation was still running, the outcome never arrived — leaving the transaction unresolved on your side with nothing left to wait for. A decision left provisional for more than five minutes is now finalized automatically as `ALLOW`, the same answer Specter already gives when a backend times out, and the `decision.async.allow` event is delivered to your webhook as usual. The backend result carries `error: "stranded"`, so an interrupted evaluation is distinguishable from one that ran to completion when you reconcile. An interruption on our side is not a failure of your backend. `stranded` does not count towards backend error rates and does not appear in the error views; in the console it reads as "interrupted by restart". ## Webhook Destinations Are Checked Before They Are Saved Registering or updating a webhook whose destination is not a complete `http` or `https` address is now refused with a `422` at the moment you save it. Previously the destination was stored as given, and the mistake surfaced only as notifications that could never be delivered — with nothing in the registration to show why. Clearing a destination entirely returned a server error rather than a validation message. Webhooks registered before this change keep running. Their destination is checked the next time you edit them. ## A Schema That Registers Is a Schema That Works Two kinds of custom schema document were accepted at registration and then failed when Specter came to use them — one at the first decision, one as a server error. Both are now refused at registration with a `422` naming the part of the document at fault, so a successful registration means the schema is usable. Both cases involve `$ref`: a reference to a key whose name would need JSON Pointer escaping (`~0`, `~1`) is not supported, and a reference that resolves to something other than a schema object is rejected. See [Schemas](/products/specter/concepts/schemas) for how documents are registered and published. ## Improvements and Bugfixes * A decision is available to read the moment you receive it. The decision record was previously written in the background, so a lookup made immediately after a decision could come back empty. Automatic blacklist population can no longer be lost either — it is committed together with the event that triggers it. * Simulation rejects a payload carrying a card number exactly as a live decision does, so a simulation you rely on before going live matches what the real endpoint will do. A card-number field is recognized whatever name it is bound under. * The rule editor no longer offers a comparison it will refuse to save. Pairing a literal value against a card-data path is blocked while you build the condition, instead of failing at save. * Signing in to the console works for accounts holding the full set of permissions; some sign-ins previously failed outright. Existing sessions are unaffected. * A value entered outside its permitted range in the console returns a validation message rather than an unexpected error. ## Blacklist Entries Carry Data Classifications Every blacklist entry now carries `value_classification` on its API representation — the `x-data-classification` the entry's `field_path` resolves to across the registered schemas: `any` or `pii`. A path no schema declares is `any` (an omitted marker is a positive assertion, and an entry on such a path is inert, since undeclared keys never enter a decision payload). The marking is stamped when the entry is written and re-resolved whenever a schema is registered or published, so it always follows the schemas' current declarations. Existing rows are classified automatically; no action is needed. In the other direction, a schema registration or publication that would re-classify a currently-blacklisted path as `pci-pan` or `pci-sad` is refused with `422` naming the path. The retention posture is now a documented statement: blacklist entries are a fraud-prevention record, exempt from the decision-record retention windows. An expired entry stops matching but its value remains in the table — a TTL does not bound retention, and deleting the entry is the only erasure. Entry values remain readable in clear on the Admin API and UI so the list stays inspectable and a data subject's entries can be found and deleted on request; every read of a value classified as personal data is recorded as an audit event. ## Interceptors Enforce the Built-in-Binding Contract Interceptors have always mapped onto the built-in `transaction` schema; that contract is now enforced instead of degrading silently. Creating or updating an interceptor rejects a `field_mapping` target the transaction schema does not declare with `422` naming the offending `field_mapping[i].target` — dynamic `metadata.` targets remain accepted. Mappings stored before this gate are not swept; they surface on their next edit. Creating or updating an interceptor whose context currently resolves to a custom-bound ruleset now rejects with `422` naming the context and the bound schema — such an interceptor could never execute, so the mistake surfaces at authoring. A context with no active ruleset yet is accepted, since its binding is not knowable at that point. If a context is rebound to a custom schema after the interceptor exists, executing it refuses with `502 INTERCEPTOR_BINDING_UNSUPPORTED`, naming the context and the bound schema — previously every mapped target was silently dropped as undeclared and the decision evaluated a near-empty payload. Passthrough interceptors (empty mapping) never touch the binding and are unaffected. The mapping editor is now tier-aware: below SAQ-D, `credential.pan.*` targets are withheld from the offered list (the served target schema carries a `withheld` flag), the "Full PAN" template is hidden, and `pan` leaves the credential-type selector — while stored mappings on withheld targets stay readable with a badge rather than degrading to unknown strings. ## Ruleset Request Shape Covers All Four Rule Types The request body for `POST /api/admin/rulesets` and `PATCH /api/admin/rulesets/{id}` now documents what Specter has always accepted: `type` is `condition`, `backend`, `backend_group`, or `blacklist` — the specification previously listed only the first two — and the fields each type carries are on the request shape, not only on the response: `fields`, `ttl_seconds`, and `populate_on` for blacklist rules; `strategy`, `members`, and `merge` for backend groups; `live` for backend rules. `id` and `name` are marked required, as they have always been, and so is the fail posture: `on_error` is required on backend and backend-group rules (never defaulted to `allow`), and `action` is required on blacklist rules as on condition rules. A backend group carries at least 2 `members`; a `split` group requires a `weight` of at least 1 on every member (ignored for `fallback` and `parallel`); and `merge` on a non-`parallel` strategy is rejected, not ignored. Two behaviors are documented for the first time. A backend or backend-group rule may carry a `condition` that **guards** the call: when it is false or abstains, the backend is skipped with a `guard_not_met` result and evaluation continues. And `backend_results[].error` documents the no-call codes — `insufficient_context`, `guard_not_met`, `backend_capability_disabled`, `backend_disabled`, and `not_simulated`; invocation failures additionally surface transport-level codes such as `timeout`, `network`, and `http_error`. Documentation only; no endpoint changed. Regenerate clients to pick up the request shape. ## Disabled Async Backend Rules No Longer Affect the Verdict A backend rule with `async: true` and `enabled: false` no longer turns an `ALLOW` into `PROVISIONAL`. Previously the rule's mere presence in the ruleset put the decision on the asynchronous path even though the rule itself never ran. A disabled rule now has no effect of any kind on the outcome, on the decision API and in the simulation trace alike. ## Contract Corrections: Filtered Operands, HMAC Literals, Markers A documentation-only round that aligns the API reference with the behavior that shipped. No endpoint changed; if you generate clients from our OpenAPI document, regenerate to pick up the corrected shapes. * **Filtered comparison operands use `expr`.** A field reference that carries a filter pipe chain is written `{"expr": "$.customer.email | downcase()"}` (long form `{"type": "filter_expr", "expr": …}`). `{"path": …}` takes a bare path only — a pipe inside it rejects as an undeclared path. Velocity fields, blacklist rule fields, and HMAC inputs keep taking the pipe string directly. * **HMAC fingerprints are `hmc_`-prefixed.** A literal you compare against an `hmac` operand must read `hmc_` followed by the 64-character digest. The simulation trace and decision evidence render the bare digest; prefix it when you copy it into a rule. HMAC inputs may also include string literals (`{"type": "const", "value": …}`) alongside at least one field reference. * **Classification markers completed.** Fourteen fields of the built-in transaction schema that Specter treats as `pii` were shown without a marker: `customer.id`, the `city` / `country` / `postal_code` / `state` leaves of `billing` and `shipping`, `credential.masked_pan.first_six` / `last_four`, `credential.paypal.payer_id`, and `frequent_flyer_number` / `country_code` on airline passengers. On decision reads, `customer_id` is `pii` as well. * **Smaller fixes.** `shipping.destination_types` is an array of strings; `masked_pan.first_six` is exactly six digits; a line item needs `name` or `sku`; phone examples are E.164; `on_error: review` is accepted when creating a rule; schema versions have no draft state — every publication creates an immutable version, `active` until deprecated; the schema-registry list endpoints are not paginated (the documented `after` / `limit` parameters never existed); `carry_over` governs velocity counters only, never blacklist entries; a `context` key in a simulation payload is ignored rather than rejected; the interceptor endpoint documents its `422` (validation / PCI gate) and `502` (field-mapping fault) responses. ## Velocity Counters Honor `carry_over` Publishing a schema version with `carry_over: false` now does what it says: rulesets bound to the new version count from zero, while counters of earlier versions are untouched and keep serving the rulesets still bound to them. With the default `carry_over: true`, counters for paths present in both versions continue across the version boundary. Until now the flag was accepted and stored but had no effect on counting. Blacklist entries are global and not versioned, so they are never affected by this flag. ## Backend Results Carry the Backend's Reference Each `backend_results` entry now surfaces `backend_reference` as a top-level field: the reference the backend itself assigned to its evaluation, the value you quote when correlating a Specter decision with the backend's own records. Previously the reference was only readable inside `raw_response`, the backend's verbatim body. The dedicated field is stable across backends and stays addressable even when the raw body is not served. `null` when the backend supplied no reference. Additive — existing consumers are unaffected. ## Stored Payload Redaction Follows the Classification Markers The payload copy stored on an interceptor decision is now redacted by the bound schema's classification markers — the same markers shown on the API reference — replacing the old name-and-shape guesswork. What this protects better: everything marked `pii` or `pci-*` is redacted by declaration — customer ids, device IP, addresses, passenger details — whether or not the field's name suggests it. What becomes visible: unmarked fields now store verbatim, including short numbers the old heuristic hid on suspicion alone, such as amounts, counts, and numeric references or metadata values. Two protections are independent of markers and unchanged in intent: PAN-like and IBAN-like values are removed wherever they appear — now including space- or dash-delimited and numerically encoded forms — and a mapping row flagged `sensitive: true` always redacts its target. One behavior is deliberately gone: Specter no longer guesses sensitivity from a source field's name. A field named `cvv` or `csc` is not special — if a mapped source carries data that must not be stored, set `sensitive: true` on that mapping row. That flag is the supported mechanism, and the only one. Payloads stored before this change keep their original redacted form. ## Regulated Data Ages Out of Decision Records Decision records now observe a per-scope retention window, configured on your instance as a number of days per classification (`pii`, `pci-pan`, `pci-sad`). Once a scope's window has passed, every field of that scope reads as `null` on `GET /api/decisions/{id}` — for the decision record that is the `pii` set: `customer_id`, `device_ip`, `device_fingerprint`; no `pci-pan` or `pci-sad` value is ever persisted on the record, so those windows bound stored payloads only. The key stays and the rest of the record — including the unmarked `masked_credential` — is untouched. The boundary is enforced on every read, not only when the background sweep reaches the row. A scope you do not configure is retained without bound, and `any`-scoped fields — amounts, references, the credential fingerprint — are never aged out, so an old decision stays correlatable. `backend_results[].raw_response` is the one place no marker reaches: the body is the backend's own shape, so it is nulled on the first configured window to expire. `backend_reference` is held outside the body and survives it. ## Regulated Fields No Longer Compare Against a Literal A rule that compares a `pci-pan`- or `pci-sad`-scoped field against a literal value now rejects at submission with `422`, on every tier. The reason is the literal, not the field. Writing `credential.pan.value == "4111111111111111"` or `cvc == "123"` stores that value in the rule itself, where it comes back on every read of the ruleset and renders in the rule editor — the one place regulated card data was still able to come to rest. Card and authentication data are never persisted, and a rule is no exception. The field stays addressable. What changes is what may sit on the other side of the comparison: * compare it against **another field reference** — that stores no value; * hash it with `hmac` and compare the digest against a literal (the `hmc_`-prefixed fingerprint) — a digest is not regulated data; * for card matching specifically, use the credential fingerprint. Nothing else moved. `pii` fields still take a literal, and velocity thresholds and windows are numbers rather than compared values, so counters are unaffected. Rules already stored keep evaluating; the check runs when you next save the ruleset, so an existing rule of this shape surfaces the first time you edit it. ## `backend_options` Removed From the Decision Request `backend_options` is gone from the decision request. The field was specified as a passthrough for provider-specific values, but nothing ever read it: no backend received it and no rule could reference it, so anything you sent in it was accepted and discarded. Sending it remains harmless. Unknown fields on the decision request are ignored, so existing integrations keep working untouched and no request starts failing because of this change. Only the specification changes: if you generate clients from our OpenAPI document, the field disappears on the next regeneration. Provider-specific data belongs in a named field instead. If you were relying on `backend_options` to reach a provider, talk to us and we will model the field properly. ## List and Object Fields Belong on One Side of `in` An array- or object-typed field is now addressable in a single operand position: the **right** side of `in` / `not_in`, the set that membership is tested against. `"visa" in credential.pan.schemes` is the shape, and it is unchanged. Everywhere else the field stands for one value, which a list or an object is not — so the submission rejects with `422` naming the declared type. That covers a scalar comparison (already the case), and now also the **left** side of `in` / `not_in`, a **velocity field**, and a **blacklist rule field**. Each of those could only ever produce "no opinion" at decision time, so a rule written that way never did anything. **Blacklist entries follow the same rule.** `POST /api/admin/blacklist` now rejects an array- or object-typed `field_path` with `422`: an entry matches exactly on the pair of `field_path` and `value` against one resolved value, so a list can never equal the stored string. Such an entry was stored and shown but could never block anything. Entries carry no schema binding, so the check is conservative across your whole registry — a path that any registered document and version types as a list or an object cannot key an entry, which means every container path of any registered document is unkeyable. The rule editor and the entry picker already offered these fields in the positions that accept them, so anything built in the UI is unaffected. Rules and entries already stored keep working as before; the check runs when you next save the ruleset or create an entry. ## `scheme` Removed From Card Credentials The singular `credential.pan.scheme` / `credential.masked_pan.scheme` string is gone; use the `schemes` array, which carries the same fact and covers co-branded cards. Requests that still send `scheme` are not rejected — the field is dropped like any other undeclared key — but a rule referencing `$.credential.pan.scheme` no longer submits. Rewrite such rules as membership over the array: `"visa" in $.credential.pan.schemes`. A rule already saved against the old path keeps evaluating and abstains, because the path now resolves absent (tri-state semantics); re-submitting that ruleset rejects until the reference is rewritten. ## Classification Markers on the Decision Payload The API reference now shows a classification marker on every field of the built-in decision payload that carries regulated data. `credential.pan.value` is marked `pci-pan`; the personal data you send for evaluation is marked `pii` — customer names, email, date of birth, billing and shipping addresses, phone numbers, device IP, and the passenger details on an airline booking. Fields without a marker hold none of those categories. `masked_credential` is the case worth knowing: `411111 •••••• 1111` identifies nobody on its own, so it carries no marker even though it looks card-like. The [full legend](/products/specter/api-reference/getting-started#data-classification) is on the API reference. Nothing about any endpoint changed — this is documentation only. ## Credential Modelled as a Discriminated Union `credential` on the decision request is now specified as a discriminated union on `type`, with one variant per credential kind — `pan`, `masked_pan`, `sepa`, and `paypal`. The request shape is unchanged: the same `type` value paired with the same sibling object is what you have always sent. What changes is that the pairing is now expressed in the specification rather than only in prose, so the reference documentation shows one tab per credential kind instead of four always-optional objects, and generated clients get a proper tagged union. The specification now rejects combinations that were never valid but that the previous shape technically allowed — most notably a `type` with no matching credential object. If you generate clients or validate against our OpenAPI document, regenerate against the current version. ## Rulesets Declare Their Schema The schema binding announced on August 7 is now enforced end to end. * **`schema` is required when you create a ruleset.** `POST /api/admin/rulesets` without it rejects with `422`; send `{"schema": {"name": "transaction"}}` for the built-in payload, or `{"name": …, "version": N}` for a custom schema. Rulesets created before this change were backfilled with the `transaction` binding and keep working. * **Rule paths validate against the bound schema.** A field reference the bound schema does not declare rejects at submission with `422` naming the path — a typo surfaces when you save, not as a silent abstention at decision time. `$.metadata.*` stays free-form. Existing rulesets are checked the next time you save them. * **Velocity fields follow the schema.** Any declared scalar field can be counted, not only the four launch paths; `pci-pan`- and `pci-sad`-scoped fields are refused, because a counter key persists the counted value. * **Decisions carry their schema.** Decision responses and `GET /api/decisions/{id}` include `schema` — the `(name, version)` the decision was evaluated against. Additive; existing consumers are unaffected. ## See Why a Rule Fired Decisions can now carry **evidence**: the request payload as it stood when the decision was made, and — for each rule that fired — the values behind it. `GET /api/decisions/{id}/payload` returns the stored request, and `GET /api/decisions/{id}/evidence` returns per-rule evidence together with a replay of the exact ruleset version that decision was pinned to. Both read under the existing `decisions:read` scope. Nothing is kept by default. Retention is opt-in per data classification, and what is stored is decided by each field's classification: `any` keeps the value, `pii` keeps the path with a redaction sentinel, `pci-pan` keeps only the fingerprint, and `pci-sad` is never stored at all. Because the sensitive tiers are already reduced when written, evidence works on instances below SAQ-D. ## Typed Schema Fields and Data-Classification Markers Custom schema fields now declare a type from a closed vocabulary — from `email`, `iban`, and `phone` to `amount`, `pan`, and `masked-pan` — and Specter validates incoming payloads against it at ingestion: Luhn checks for card numbers, ISO 8601 for dates, check digits for IBANs, closed enums for schemes and funding types. Each field also carries a data-classification marker (`pci-pan`, `pci-sad`, `pii`, or none) that governs where its value may flow: marked values are redacted from error bodies, logs, and audit events, `pan` fields are always PCI-scoped, and sensitive authentication data is never stored — below SAQ-D it is rejected outright, and nothing that persists data (velocity counters, blacklist entries) may reference a `pci-sad` field. ## Built-In Transaction Schema The decision payload shape Specter ships with is now a first-class schema: read-only, introspectable through the schema registry like any custom schema, and evolving additively only — existing fields never change type or disappear. Rulesets that name no custom schema are backfilled to bind the built-in, so every ruleset now states what it evaluates against. ## BIN Attributes on Card Credentials Card credentials now expose BIN-derived attributes — `bin`, `schemes`, `type`, `segment`, `currency_code`, `issuer_name`, and `issuer_country_code` — on both `credential.pan` and `credential.masked_pan`, ready to use in rule conditions. All attributes are optional and reflect the BIN data as provided: Specter does not curate, reconcile, or guarantee consistency between them. `schemes` is an array — match it with the `in` operator. When simulation detects diverging BIN attributes, the trace carries a `bin_attribute_divergence` warning. ## Custom Decision Schemas Define the shape of your own decision payloads with the new schema registry under `/api/admin/schemas`. Schemas are named and versioned: every publication creates a new, immutable version, and a superseded version can be deprecated while it keeps serving the rulesets bound to it. A ruleset binds one `(schema, version)` pair — the binding validates incoming payloads and resolves rule field paths. The identity fields of a persisted ruleset (`schema`, `context`) no longer change on `PATCH`: sending a different value returns a `422`, resending the same value is a no-op. The registry endpoints require the new `admin:schemas:read` and `admin:schemas:write` scopes. ## Filter Pipes and HMAC Fingerprints in Rule Conditions Field references in rule conditions now accept filter pipe chains — `$.customer.email | trim() | downcase()` — wherever a field path appears: condition operands, velocity fields, and HMAC inputs. The v1 filter set is closed and typed; filters compose left to right, and a filter receiving an absent or malformed value abstains rather than failing the rule. Filtered values inherit the strictest data-classification marker of their inputs. A new `hmac` operator composes an ordered list of filtered field references into a deterministic HMAC-SHA256 fingerprint, so rules can correlate on stable derived identifiers without exposing the underlying values. ## Simulate Rulesets Without Side Effects The new `POST /api/admin/rulesets/{id}/simulate` endpoint evaluates a test payload you supply against any persisted ruleset — `DRAFT`, `ACTIVE`, or `INACTIVE` — and returns a full evaluation trace: the verdict the ruleset would reach, and for every rule whether it fired, abstained, or was skipped, and why. Simulation is inert — no decision is recorded, no velocity counter is incremented, and no backend is invoked. Velocity conditions read live counter state and report the value they evaluated against alongside the rule's threshold and rolling window, so the trace shows how close a rule was to firing. An abstention always names its cause: `absent_fields` where a field the rule addressed was not in the payload, and `absorbed_operands` where one was present but could not be used — a membership test whose set operand is not an array, for instance, which otherwise looks like a rule that silently never matches. Simulation requires the new `admin:rulesets:simulate` scope. ## PayPal Payment Method Type Decision requests now accept a `paypal` credential alongside `pan`, `masked_pan`, and `sepa`. The credential carries `email`, `payer_id`, `address_status`, `protection_eligibility`, and `payer_status` — all optional, all caller-injected from your PayPal flow, and all addressable in rules as `$.credential.paypal.*`. Send whichever your flow has captured at the point you call Specter. The email is never persisted raw; the decision log keeps the credential fingerprint (computed from `payer_id`) and a masked form. See [Credential types](/products/specter/integration/via-api#credential-types). ## Report Fraud on Any Decision `fraud_report` events are no longer tied to the payment lifecycle: they are accepted in any payment state — including for decisions that never received a lifecycle event — so you can feed fraud signals back to Specter even if you only use it for screening. An accepted report sets a permanent fraud marker on the decision (`fraud_marked_at`, `fraud_source`, `fraud_reason`), independent of `payment_state`, and continues to fan out to backends and auto-populate the [blacklist](/products/specter/concepts/blacklist). See [lifecycle events](/products/specter/concepts/lifecycle-events#fraud-reports). ## Smarter Condition Fields in the Rule Editor The rule editor's condition field suggestions are now served by the backend, so the type-ahead always matches the exact request shape your rules evaluate against — including nested credential fields and your `$.metadata.*` paths. Fields that are unavailable on your instance, such as raw PAN paths below SAQ-D, are omitted automatically. ## Run Specter Within SAQ A and SAQ A-EP Scope Specter now runs on instances assessed at SAQ A or SAQ A-EP — you no longer need a SAQ-D environment to use it. Below SAQ-D, Specter actively enforces the boundary that keeps your assessment scope small: request bodies carrying a raw card number (PAN) outside the designated credential field are rejected with a `422` validation error, and Interceptor payloads are scanned so raw card data never reaches your instance. The platform test-card policy applies on every instance: on the Test tier, decision requests accept only known test cards; on the Production tier, test cards are rejected. Violations return a `403` with a clear classifier. ## Specter Is Live Specter — the risk and fraud decisioning component of the [Composable Payment Architecture](/platform/cpa) — is now generally available on the Test and Production tiers. Evaluate payments against your own [rulesets](/products/specter/concepts/rulesets) and get an allow, block, or review [decision](/products/specter/concepts/decisions) in real time. Integrate through the [Decision API](/products/specter/integration/via-api), or drop the [Interceptor](/products/specter/integration/via-interceptor) into your existing PSP traffic without changing your payment flow. External risk engines plug in through [Link](/products/link/overview) backends. Start with the [overview](/products/specter/overview) and [plans and access](/products/specter/plans-and-access). # Backends Source: https://developer.hellgate.io/products/specter/concepts/backends How Specter connects to external risk backends through Link integrations. A **backend** is an external risk engine that scores transaction data as input to a decision. Specter does not integrate any backend directly — it connects through a [Link](/products/link/overview) instance, which hosts the backends and exposes them to Specter. Specter discovers them and makes them available to your [backend rules](/products/specter/concepts/rule-engine). Backend rules are available on rulesets bound to the built-in `transaction` [schema](/products/specter/concepts/schemas); a ruleset bound to a custom schema cannot carry one. ## How it works 1. Every Specter instance comes with a **bundled** [Link](/products/link/plans-and-access#editions) instance that serves its backends and scales with it — there is no separate Link contract to arrange. 2. The Specter protocol is enabled on that instance and your chosen backends are configured. 3. Specter registers the Link instance as an integration and discovers the backends it exposes. 4. The discovered backends become available to your rulesets automatically. The bundled Link instance is locked to serving your Specter backends. To run other protocols or send other load through it, unlock it into a [Standalone](/products/link/plans-and-access#editions) instance — see [Link Plans & Access](/products/link/plans-and-access). ## Available backends The catalogue of backends grows continuously — for example VISA Decision Manager, Ravelin, and Worldpay FraudSight. Contact your account representative for the current list and help choosing the right backend for your requirements. ## What a backend receives Specter forwards the decision context to the bundled Link instance, where the installed backend configuration maps it into the vendor's request. What a vendor receives is decided by that configuration — not by Specter, and not by the vendor: * With a `pan` credential, the full PAN, its expiry, and the cardholder name are forwarded to Link and are available to every backend configuration on your instance. A configuration may pass the PAN on unchanged or reduce it — for example to BIN and last four — before it reaches the vendor. * With a `masked_pan` credential, no backend can receive the PAN: only the first six and last four digits and the expiry ever leave Specter. A backend configuration is either **self-provisioned** — you author and install it, and can inspect its mapping at any time — or **managed** by Hellgate on your behalf, in which case Hellgate can state the mapping on request. See [Link backends](/products/link/concepts/backends#provisioning-self-vs-managed). ## Context validation and fail-open Before calling a backend, Specter validates that the decision context contains the fields the backend requires. If a required field is missing, no call is made: the `backend_results` entry records an `insufficient_context` error so you know exactly what to add. Backend rules also carry an `on_error` setting (`allow`, `block`, or `review`) that governs what happens when a backend errors or times out — `allow` skips the backend so an infrastructure failure never blocks a customer. ## Composing backends A single [backend rule](/products/specter/concepts/rule-engine) calls one backend. A **backend group** rule orchestrates several — the foundation for migrating from one backend to another, A/B testing, and high-availability setups. A group runs its members with one of three strategies: | Strategy | Behavior | | - | - | | `fallback` | Try members in order; use the first live member that returns a result. If one errors or lacks context, try the next. | | `parallel` | Call all members at once and combine their outcomes with a `merge` strategy. | | `split` | Route each request to exactly one member, chosen by weighted random distribution. | For a `parallel` group, the `merge` strategy decides the combined outcome: | Merge | Outcome | | - | - | | `strictest` | The most severe outcome wins (`BLOCK` > `REVIEW` > `ALLOW`). | | `most_lenient` | The least severe outcome wins. | | `consensus` | The shared outcome if all members agree, otherwise `REVIEW`. | ### Shadow members A group member can run in **shadow mode** (`live: false`): it executes and its result is recorded in `backend_results`, but it does not contribute to the decision. Shadow members let you trial a new backend against live traffic before trusting it. (In a `split`, the selected member always runs live.) ### Asynchronous groups A backend group can run **asynchronously** (`async: true`). Specter returns a `PROVISIONAL` decision from the local rules immediately and evaluates the group in the background, delivering the final outcome by webhook — see [Asynchronous decision with delayed capture](/products/specter/integration/via-api#asynchronous-decision-with-delayed-capture). ## Configuration Backends are reached through **integrations** — connections to your Link instance, managed through the Admin API (scopes `admin:integrations:read` / `admin:integrations:write`). A single integration can expose several backends, and each backend rule selects one to invoke. Register a Link instance and discover its backends in the API reference. # Blacklist Source: https://developer.hellgate.io/products/specter/concepts/blacklist Block transactions by field value, with TTLs and event-driven auto-population. The blacklist lets operators block transactions based on values extracted from the decision payload — a card fingerprint, a device IP, a customer ID, and so on. Entries can be created manually through the Admin API or populated automatically from [lifecycle events](/products/specter/concepts/lifecycle-events). ## Entries A blacklist entry blocks a specific value at a given JSONPath, optionally with an expiry. ```json theme={null} { "field_path": "$.credential_fingerprint", "value": "crd_4ba218...", "ttl_seconds": 604800 } ``` JSONPath matched against the decision payload (e.g. `$.device.ip`, `$.customer.id`). An entry matches one stored value against one resolved value, so the path must resolve to a scalar: array- or object-typed paths reject with `422`, as do `pci-pan`- and `pci-sad`-scoped paths — an entry persists the raw value, and card blacklisting rides `$.credential_fingerprint` instead. Entries carry no schema binding; the check is conservative across every schema registered on your instance. The exact value to block. Time-to-live. Omit for a permanent entry. Manage entries with the Admin API (scopes `admin:blacklist:read` / `admin:blacklist:write`): | Method | Path | | - | - | | `GET` | `/api/admin/blacklist` | | `GET` | `/api/admin/blacklist/{id}` | | `POST` | `/api/admin/blacklist` | | `DELETE` | `/api/admin/blacklist/{id}` | For card-fingerprint entries, Specter stores a masked-PAN `display_hint` (e.g. `****1234`) alongside the opaque fingerprint so operators can identify the card without exposing it. ## Data classification and retention Blacklist entries are a **fraud-prevention record**: they are exempt from the decision-record retention windows and persist indefinitely by design. `ttl_seconds` stays optional and merchant-set, and no sweeper runs over the table. An expired entry stops matching but its value **remains in the table** — a TTL does not bound retention; deleting the entry is the only erasure. Entry values are **readable in clear** on the Admin API and UI, including values classified as personal data — the operator who owns the blacklist must be able to inspect what is blocked and, on a data subject's request, find and delete their entries, which a redacted or hashed value would prevent. Every read of a value classified as personal data is recorded as an audit event. To make those entries findable, every row carries a classification field on its API representation: The `x-data-classification` that `field_path` resolves to across the registered schemas: `any` or `pii`. A path no schema declares is `any` — an omitted marker is a positive assertion, and an entry on such a path is inert anyway, since undeclared keys never enter a decision payload. The marking is stamped when the entry is written and re-resolved whenever a schema is registered or published, so it always reflects the schemas' current declarations rather than the ones in effect when the row was written. The reverse interaction is guarded too: a schema registration or publication that would re-classify a currently-blacklisted path as `pci-pan` or `pci-sad` is refused with `422` naming the path — never-store data cannot enter the blacklist by write, and cannot be created in place by re-declaration either. ## Blacklist rules A `blacklist` rule in a ruleset blocks any decision whose payload contains a matching value, and can **auto-populate** the blacklist when a configured event arrives: ```json theme={null} { "id": "block-known-cards", "type": "blacklist", "name": "Block known fraudulent credentials", "action": "BLOCK", "enabled": true, "fields": ["$.credential_fingerprint", "$.device.ip"], "ttl_seconds": 604800, "populate_on": ["fraud_report", "chargeback"] } ``` JSONPath fields checked against the blacklist on each decision. Each may carry a filter pipe chain (`$.customer.email | downcase()`), and the same scalar and scope gates as entries apply. TTL applied to auto-populated entries. Omit for permanent. Event types that trigger auto-population: `fraud_report`, `chargeback`, `failed`. Defaults to `["fraud_report"]`. When a matching [lifecycle event](/products/specter/concepts/lifecycle-events) is ingested, Specter upserts blacklist entries for every configured field using the originating decision's values. A card that charges back is then blocked on its next attempt. ## Related The events that drive blacklist auto-population. # Lifecycle Events Source: https://developer.hellgate.io/products/specter/concepts/lifecycle-events Feed transaction outcomes back to Specter to close the fraud feedback loop. Fraud models and rules improve when they learn what happened **after** a decision. Lifecycle events report the real outcome of a transaction — authorization, capture, chargeback, and more — back to Specter, which fans them out to participating backends and uses them to auto-populate [blacklists](/products/specter/concepts/blacklist). Submit an event with `POST /api/events`. Events are processed **synchronously**: the response includes a `backend_notifications` entry for every backend call made before returning. Retrieve a persisted event with `GET /api/events/{id}`. Lifecycle events exist for the built-in `transaction` [schema](/products/specter/concepts/schemas) only. An event that references a decision evaluated under a custom schema is rejected with `422`, and such decisions never auto-populate the blacklist. Custom schemas declare the decision request only. ## Event types | Type | Description | | - | - | | `authorization` | Card authorization outcome (AVS/CVV results reveal data quality). | | `capture` | Payment captured. | | `refund` | Amount refunded. | | `void` | Authorization voided. | | `chargeback` | Chargeback filed — the ground truth for fraud. | | `fraud_report` | An explicit fraud report. | Lifecycle event types must be a valid step in the payment's lifecycle (an authorization before a capture, and so on); events that are not are rejected. `fraud_report` is the exception — see below. ## Fraud reports Fraud is not a payment-lifecycle state: a transaction can turn out fraudulent whether it was captured, merely authorized, or never reported to Specter beyond the original decision. A `fraud_report` event is therefore accepted in **any** payment state — including for decisions that never received a lifecycle event at all — and never changes `payment_state`. Accepting a fraud report sets the decision's **fraud marker**, returned on the decision resource: | Field | Value | | - | - | | `fraud_marked_at` | When the report was accepted. | | `fraud_source` | Copied from the event's `data.source`, when present. | | `fraud_reason` | Copied from the event's `data.reason`, when present. | The marker is permanent and first-write-wins: repeat reports are accepted and kept in the event log, but the marker keeps the values from the first report. Like every `fraud_report`, they also fan out to backends and can auto-populate the blacklist. ```json theme={null} { "type": "fraud_report", "decision_id": "dec_01J...", "occurred_at": "2026-07-28T12:00:00Z", "data": { "source": "issuer_report", "reason": "stolen card" } } ``` ## Request An event references the originating `decision_id`; its `data` is a free-form map stored with the event and forwarded to backends that support the event type. ```json theme={null} { "type": "chargeback", "decision_id": "dec_01J...", "occurred_at": "2026-03-17T12:00:00Z", "data": { "amount": 1999, "reason": "4853" } } ``` ## Backend fan-out Specter forwards each event to every backend that participated in the original decision and supports the event type. Backends are reached through a [Link integration](/products/specter/concepts/backends). A backend declares which event types it supports. When an event targets a type a backend does not support, Specter records `status: "skipped"` — not an error. The `backend_notifications` array in the response reports the outcome of every backend call. ## Blacklist auto-population A [blacklist rule](/products/specter/concepts/blacklist) can listen for specific event types (`fraud_report`, `chargeback`, `failed`) and automatically add the originating decision's field values to the blacklist — so a card that produces a chargeback is blocked on its next attempt. ## Related Auto-populate blocks from fraud and chargeback events. Resolve REVIEW decisions and notify backends. # Overview Source: https://developer.hellgate.io/products/specter/integration/overview Choose between the API and interceptor integration patterns. Specter offers two ways to get a decision. Both evaluate the same rulesets and return the same outcomes; they differ only in **where the integration lives**. The API pattern works for any [schema](/products/specter/concepts/schemas) — the built-in transaction payload or one you register; the interceptor pattern applies to card-payment flows on the built-in transaction schema. | Pattern | How it works | Code changes | Best for | | - | - | - | - | | [API](/products/specter/integration/via-api) | Your system calls `POST /api/decisions` before authorizing, reads the outcome, and acts on it. | Your backend builds the request and branches on the result. | Teams that own their payment flow and want full control. | | [Interceptor](/products/specter/integration/via-interceptor) | Specter sits inline as a transparent proxy in front of your payment destination — acquirer, PSP, or gateway. It evaluates the request and either forwards it (`ALLOW`) or returns a block/review response. | Point the destination URL at Specter — no backend logic. | Adding fraud checks with minimal or no integration work. | ## API You stay in full control of the flow: call Specter, inspect the decision, and decide whether to authorize, hold for review, or stop. This gives you the richest request (you choose exactly what context to send) and the most flexibility in how you handle each outcome. ```mermaid theme={null} sequenceDiagram participant M as Your system participant S as Specter participant A as Acquirer M->>S: POST /api/decisions S-->>M: ALLOW / REVIEW / BLOCK alt ALLOW M->>A: Authorize else BLOCK M-->>M: Stop payment end ``` ## Interceptor Specter terminates the request, maps it into a decision, evaluates it, and then either transparently forwards the original request to the real destination (`ALLOW`) or returns a configured response (`BLOCK` / `REVIEW`) without contacting it. Your backend needs no decision logic — it only changes the URL it calls. ```mermaid theme={null} sequenceDiagram participant M as Your system participant S as Specter participant A as Destination M->>S: Request Note over S: Map → decide alt ALLOW S->>A: Forward request unchanged A-->>S: Response S-->>M: Response else BLOCK / REVIEW S-->>M: Configured response end ``` ## Which should I use? Choose this when you control the payment backend and want maximum context and flexibility. Choose this when you want fraud protection without changing backend logic. # Via API Source: https://developer.hellgate.io/products/specter/integration/via-api Call Specter before authorization and act on the decision outcome. In the API pattern your system calls Specter directly, reads the outcome, and decides how to proceed. You own the flow and choose exactly what context to send. ## Request Send `POST /api/decisions` with the `decisions:create` scope. The `context` you name selects the active ruleset, and that ruleset's [schema](/products/specter/concepts/schemas) defines the payload. The fields below are the built-in **transaction** schema; for a context bound to a custom schema, send the fields that schema declares in the same envelope. On the transaction schema three domains are required; everything else enriches the evaluation and is forwarded to backends where applicable. The payment credential. `type` is `pan`, `masked_pan`, `sepa`, or `paypal` — see [Credential types](#credential-types). Must include `id`. May include `email` and `date_of_birth`. `reference`, `amount` (minor units), and `currency` (ISO 4217). `ip`, `fingerprint`, `user_agent`, `session`, and `language`. Billing address, forwarded to applicable backends. Shipping address, forwarded to applicable backends. Cart line items; each requires at least `name` or `sku`. Travel data — passengers and legs — forwarded to backends that consume it. Flat string key-value pairs, available to rules as `$.metadata.*`. Forwarded to backends and stored on the decision record, so treat it as data you share with your backend providers. Selects the active ruleset to evaluate — and through it, the schema. The complete request and response schema, including every nested field, is in the [API reference](/products/specter/api-reference/decisions/create-decision). ### Credential types Full card number. Used to evaluate the transaction and compute the credential fingerprint; **never persisted**. ```json theme={null} { "type": "pan", "pan": { "value": "4111111111111111", "expiry_month": 12, "expiry_year": 2030, "schemes": ["visa"], "cardholder_name": "Jane Doe", "bin": "411111", "type": "credit", "segment": "consumer", "currency_code": "EUR", "issuer_name": "Example Bank N.A.", "issuer_country_code": "DE" } } ``` The seven BIN attributes — `bin`, `schemes`, `type`, `segment`, `currency_code`, `issuer_name`, `issuer_country_code` — are optional and named exactly as Guardian's [metadata inquiry](/products/guardian/tokens/metadata-inquiries) returns them, so you can pass that response through. Specter performs no BIN lookup of its own; rules read whatever you send. First six and last four digits only — use when you do not hold the full PAN. ```json theme={null} { "type": "masked_pan", "masked_pan": { "first_six": "411111", "last_four": "1111", "expiry_month": 12, "expiry_year": 2030, "schemes": ["visa"], "bin": "411111", "type": "credit", "segment": "consumer" } } ``` The same BIN attributes as on `pan` are accepted here. Bank account credential for SEPA direct debit, identified by IBAN. ```json theme={null} { "type": "sepa", "sepa": { "iban": "DE89370400440532013000", "owner_name": "John Doe" } } ``` PayPal account credential. All five fields are optional — send whichever your PayPal flow has captured at the point you call Specter. Each one you send makes the credential referenceable in more rules. As everywhere in the API, a field you don't have a value for is omitted, never sent as `""` — see [Empty Strings and Omitted Fields](/products/specter/api-reference/getting-started#empty-strings-and-omitted-fields). ```json theme={null} { "type": "paypal", "paypal": { "email": "sherlock.holmes@example.com", "payer_id": "7E7MGXCWTTKK2", "address_status": "confirmed", "protection_eligibility": "eligible", "payer_status": "verified" } } ``` All fields are **caller-injected**: you source them from your PayPal flow (for example Cybersource PayPal Express Checkout Services) and send them on the request. Specter performs no PayPal lookup. | Specter field | Source field | Values | | - | - | - | | `email` | `paypalCustomerEmail` | email address | | `payer_id` | `paypalPayerId` | string | | `address_status` | `addressStatus` | `none`, `confirmed`, `unconfirmed` | | `protection_eligibility` | `protectionEligibility` | `eligible`, `partially_eligible`, `ineligible` | | `payer_status` | `payerStatus` | `verified`, `unverified` | Enum values are canonical lowercase snake\_case: map the source casing when you populate them (`PartiallyEligible` becomes `partially_eligible`). The fields become available at different stages of the PayPal flow, which is why none of them is mandatory. `payer_id`, `email`, `address_status`, and `payer_status` are returned once the customer approves at PayPal, while `protection_eligibility` is only returned after authorization — so a pre-authorization decision usually cannot carry it. Send `payer_id` whenever you have it: it is the source of the credential fingerprint, so a decision without it has no `$.credential_fingerprint`, and velocity rules and blacklist entries keyed on that path will not apply to it. The email is PII and is **never persisted** raw. The decision log keeps only the credential fingerprint and a masked form of the email such as `s•••@example.com`. ### Example request ```bash theme={null} curl -X POST https://{instance}.eu1.on-hellgate.cloud/api/decisions \ -H "Authorization: Bearer " \ -H "Content-Type: application/json" \ -d '{ "credential": { "type": "pan", "pan": { "value": "4111111111111111", "expiry_month": 12, "expiry_year": 2030 } }, "customer": { "id": "cust_789", "email": "customer@example.com" }, "transaction": { "reference": "order-12345", "amount": 14999, "currency": "EUR" }, "context": "checkout" }' ``` ## Response Specter returns the decision together with the rules that fired and any backend results. Unique decision identifier. `ALLOW`, `REVIEW`, `BLOCK`, or `PROVISIONAL` — see [Outcomes](/products/specter/concepts/decisions#outcomes). `PROVISIONAL` only occurs with asynchronous backend groups. The ruleset context that was evaluated. The `(name, version)` the decision was evaluated against — the binding of the ruleset that served it. The ruleset that served the decision. The exact version pinned to the decision. Deterministic HMAC-SHA256 identifier for the payment credential. For `paypal` it is computed from the `payer_id`. The credential type supplied — `pan`, `masked_pan`, `sepa`, or `paypal`. Display-safe masked representation, e.g. `411111 •••••• 4242` for cards or `s•••@example.com` for PayPal. Rules that fired, in evaluation order. One entry per backend rule executed, including any error such as `insufficient_context`. Present when the ruleset includes backend rules. `null` until a `REVIEW` decision is closed — see [Resolving a REVIEW decision](/products/specter/concepts/decisions#resolving-a-review-decision). ### Example response ```json theme={null} { "id": "dec_01J...", "decision": "REVIEW", "context": "checkout", "schema": { "name": "transaction" }, "ruleset_id": "7c9e6679-...", "ruleset_version": 3, "credential_fingerprint": "crd_4ba218...", "credential_type": "pan", "masked_credential": "411111 •••••• 1111", "triggered_rules": [ { "id": "ravelin-assessment", "type": "backend", "action": "REVIEW" } ], "backend_results": [{ "backend": "link-prod/ravelin", "decision": "REVIEW" }], "resolution": null } ``` ## Use cases ### Synchronous pre-authorization decision Specter returns a definitive decision in one call. Simplest to integrate, and the right default when the added latency of any backends is acceptable. ```mermaid theme={null} sequenceDiagram participant M as Merchant participant S as Specter participant B as Risk backend participant A as Acquirer M->>S: POST /api/decisions S->>B: Evaluate (rules + backends) B-->>S: Result S-->>M: ALLOW / REVIEW / BLOCK M->>A: Authorize ``` 1. The merchant requests a decision from Specter. 2. Specter applies all rules and, where required, reaches out to backends. 3. The merchant uses the definitive result to authorize the payment. ### Asynchronous decision with delayed capture For latency-sensitive flows, Specter returns a fast `PROVISIONAL` result from local rules and finishes the backend evaluation in the background. The merchant authorizes optimistically and defers the capture until the final decision arrives. ```mermaid theme={null} sequenceDiagram participant M as Merchant participant S as Specter participant B as Risk backend participant A as Acquirer M->>S: POST /api/decisions S-->>M: PROVISIONAL (local rules) M->>A: Authorize, defer capture S->>B: Evaluate backends (async) B-->>S: Result S-->>M: Webhook: final decision M->>A: Capture or cancel ``` 1. The merchant requests a decision from Specter. 2. Specter applies all local rules and returns a `PROVISIONAL` result immediately. 3. The merchant optimistically authorizes and defers the capture. 4. Specter reaches out to backends asynchronously. 5. Specter notifies the merchant of the definitive decision through a registered [webhook](/products/specter/api-reference/webhooks/register-webhook). 6. The merchant captures or cancels the authorization based on the final decision. ## Related Outcomes, the decision log, and resolving REVIEW decisions. How your request is evaluated into a decision. # Via Interceptor Source: https://developer.hellgate.io/products/specter/integration/via-interceptor Run decisions inline as a transparent proxy in front of your payment destination. An **interceptor** is a transparent proxy. Specter sits inline between your client and a **destination** — the acquirer, PSP, or any other payment endpoint you would otherwise call directly: it receives the request body, **maps it into a standard [Decision API](/products/specter/integration/via-api) request** on the built-in transaction schema, evaluates it, and either forwards the original request unchanged (`ALLOW`) or returns a configured response (`BLOCK` / `REVIEW`) without contacting the destination. The evaluation is identical to the [API pattern](/products/specter/integration/via-api) — the same rulesets, backends, and outcomes. Only the transport differs: you point the destination URL at Specter instead of calling the Decision API yourself, so no decision logic is needed in your backend. ## Runtime flow ``` POST /api/interceptors/{ref} Authorization: Bearer Content-Type: application/json ``` Specter applies the interceptor's `field_mapping` to the inbound body to build a [Decision API request](/products/specter/integration/via-api#request). The decision request is evaluated against the active ruleset for its context. `ALLOW` → forward the original body unchanged to the destination and stream the response back. `BLOCK` / `REVIEW` → return the configured response without calling the destination. The decision is logged with `integration: "interceptor"` and the interceptor reference. ## Progressive rollout The interceptor is built to be adopted in stages, so you can de-risk each step before it touches live traffic. Each stage adds one capability on top of the last. Activate the interceptor with no [field mapping](#field-mapping). Specter forwards every request unchanged and auto-discovers the fields it sees, giving you the data to build the mapping. No decision is made, and no `x-specter-decision-id` header is added. Add the field mapping. Specter now builds and validates a decision request from each inbound body, so you can confirm the request is understood. With no blocking rules yet, traffic still flows through untouched. Add rules and backends in **shadow mode** (`live: false`). Specter evaluates and logs the decision and returns its ID in the `x-specter-decision-id` header, but shadow rules never change the outcome — so every request is still forwarded. You measure decision quality against real traffic with zero customer impact. Switch the rules and backends to live. Specter now enforces the decision: it forwards on `ALLOW` and returns the configured response on `BLOCK` / `REVIEW`. ### Transparent interceptor (shadow) Specter observes but never blocks: every request reaches the destination, and the decision rides along in the response header. ```mermaid theme={null} sequenceDiagram participant M as Merchant participant S as Specter participant B as Risk backend participant A as Destination M->>S: Request S->>B: Evaluate in shadow mode S->>A: Forward request A-->>S: Result S-->>M: Result + decision in header ``` 1. The merchant sends the request to the destination through the Specter interceptor. 2. Specter engages backends in shadow mode. 3. Specter forwards the request to the destination. 4. The destination returns the result. 5. Specter returns the destination's exact result, with the decision information in the response header. ### Active interceptor Once you trust the decisions, switch to live. Specter forwards only approved requests; everything else is short-circuited with your configured response. ```mermaid theme={null} sequenceDiagram participant M as Merchant participant S as Specter participant B as Risk backend participant A as Destination M->>S: Request S->>B: Evaluate alt decision is ALLOW S->>A: Forward request A-->>S: Result S-->>M: Result + decision in header else BLOCK / REVIEW S-->>M: Configured response end ``` 1. The merchant sends the request to the destination through the Specter interceptor. 2. Specter engages backends. 3. Specter forwards the request to the destination only if the decision outcome is `ALLOW`. 4. The destination returns the result. 5. Specter returns the destination's exact result, with the decision information in the response header. ## Configuration Interceptors are configured through the Admin API (scopes `admin:interceptors:read` / `admin:interceptors:write`) and have their own `DRAFT → ACTIVE` lifecycle with activate and rollback. ```json theme={null} { "description": "Adyen checkout interceptor", "active": true, "destination": { "url": "https://checkout-test.adyen.com/v70/payments", "timeout_ms": 5000 }, "field_mapping": [ { "source": "$.amount.value", "target": "transaction.amount" }, { "source": "$.amount.currency", "target": "transaction.currency" }, { "source": "$.reference", "target": "transaction.reference" }, { "source": "$.shopperIP", "target": "device.ip" }, { "source": "$.paymentMethod.encryptedCardNumber", "filter": "first(6)", "target": "credential.masked_pan.first_six" }, { "source": "$.paymentMethod.encryptedCardNumber", "filter": "last(4)", "target": "credential.masked_pan.last_four" }, { "const": "masked_pan", "target": "credential.type" } ], "responses": { "block": { "mode": "template", "status_code": 422, "content_type": "application/json", "body": { "message": "Request blocked by fraud rules", "reference": "{{ decision_id }}" } }, "review": { "mode": "decision" } } } ``` ## Field mapping Each entry builds part of the [Decision API request](/products/specter/integration/via-api#request) — the `target` names a field of the built-in [transaction schema](/products/specter/concepts/schemas) (`transaction.amount`, `credential.type`, …). Entries are evaluated in order and merged. Interceptors map onto the built-in `transaction` schema only; the context an interceptor names must resolve to a `transaction`-bound ruleset. This is enforced at both ends: creating or updating an interceptor rejects with `422` when a `target` is not declared by the transaction schema (dynamic `metadata.` targets are accepted, although the editor's target list does not enumerate them) or when the named context currently resolves to a custom-bound ruleset — such an interceptor could never execute. A context with no active ruleset yet is accepted, since the binding is not knowable at that point; if it later resolves custom-bound, executing the interceptor refuses with `502 INTERCEPTOR_BINDING_UNSUPPORTED` naming the context and the bound schema. Passthrough interceptors (empty mapping) never touch the binding and are not refused. | Entry | Example | Effect | | - | - | - | | Source | `{ "source": "$.amount.value", "target": "transaction.amount" }` | Copy a value from the inbound body. | | Const | `{ "const": "masked_pan", "target": "credential.type" }` | Assign a static value. | | Source + filter | `{ "source": "$.pan", "filter": "first(6)", "target": "credential.masked_pan.first_six" }` | Transform before assigning. | Source paths support `$` (root) and `$.a.b.c` dot notation. Missing paths produce no output — partial mappings are valid. Add `"sensitive": true` to a row whose value must not appear in the payload copy stored on the decision; the copy is otherwise redacted by the classification markers of the schema, never by guessing from a source field's name. ### Filters Filters chain with `|` (e.g. `"downcase | first(6)"`). These are mapping filters, distinct from the `filter()` pipes of [rule conditions](/products/specter/concepts/rule-engine#field-references-and-filters): | Filter | Description | | - | - | | `first(n)` | First `n` characters. | | `last(n)` | Last `n` characters. | | `tail(n)` | All characters after position `n`. | | `map({...})` | Map a value via a lookup table. | | `to_int` | Parse a numeric string to an integer. | | `downcase` | Lowercase the string. | ## Responses Each outcome key (`block`, `review`) maps to a response config: * **`decision` mode** — returns the standard Specter decision map as-is. * **`template` mode** — returns a static body with placeholders resolved at runtime. | Placeholder | Resolves to | | - | - | | `{{ decision_id }}` | UUID of the logged decision | | `{{ decision }}` | `BLOCK` or `REVIEW` | | `{{ source }}` | Rule source (backend name or `condition`) | | `{{ credential_fingerprint }}` | Credential fingerprint | | `{{ latency_ms }}` | Evaluation latency in milliseconds | | `{{ triggered_rules }}` | JSON array of triggered rule IDs | When no response is configured for an outcome, Specter defaults to `decision` mode. ## Header forwarding (ALLOW) On `ALLOW`, Specter forwards the request to the destination and: * Strips reserved headers: `authorization`, `content-length`, `content-type`, `host`. * Resolves `x-{label}-name` / `x-{label}-value` header pairs into a single `{name}: {value}` header — used to inject destination credentials without exposing them in the body. * Appends `content-type: application/json` and `x-specter-decision-id: `. The destination response (headers, body, status) is returned to the caller unchanged. ## Related The direct-call alternative when you control the payment backend. # Overview Source: https://developer.hellgate.io/products/commerce/v2/api-reference/authentications/overview Handle 3-D Secure authentication flows The Authentications API enables you to process EMVCo 3-D Secure authentication requests as standalone transactions, separate from payment authorization. ## Customer Initiated Authentications Commerce allows you to process EMVCo 3-D Secure authentication requests as standalone requests. For example, these endpoints can be used to process payment authentication centrally and process the subsequent authorization requests conditionally on different payment processors. The section of customer initiated authentications consists of three use-cases: * **One Off**: A standard situation in which a single payment amount shall undergo 3-D Secure authentication. * **Initial Recurring**: An authentication of the first payment in the sequence of recurring payments. * **Initial Installment**: An authentication of the first payment in the sequence of installment payments. ## Merchant Initiated (3RI) Commerce supports requestor-initiated EMVCo 3-D Secure authentication requests as standalone transactions. All requests in this section must be linked to a prior customer-initiated authentication. ## Authentication Data Access the results of prior authentications for reference and audit purposes. # Initial installment Source: https://developer.hellgate.io/products/commerce/v2/api-reference/authentications_ci/initial-installment /products/commerce/v2/openapi.yaml post /authentications/installment Request an authentication for an initial installment payment. This API endpoint triggers a 3-D Secure (3DS) authentication without processing a payment. It is used to verify a customer's identity before completing a transaction or for regulatory compliance (e.g., Strong Customer Authentication - SCA). The customer must be on-session to complete the authentication. Commerce utilizes the action_requirement mechanism to request any necessary interaction with the customer. The interactions are supported with SDKs. See the [SDK documentation](/platform/resources/sdks/web-sdk/overview) for more information. Beta Feature # Initial recurring Source: https://developer.hellgate.io/products/commerce/v2/api-reference/authentications_ci/initial-recurring /products/commerce/v2/openapi.yaml post /authentications/recurring Request an authentication for an initial recurring payment. This API endpoint triggers a 3-D Secure (3DS) authentication without processing a payment. It is used to verify a customer's identity before completing a transaction or for regulatory compliance (e.g., Strong Customer Authentication - SCA). The customer must be on-session to complete the authentication. Commerce utilizes the action_requirement mechanism to request any necessary interaction with the customer. The interactions are supported with SDKs. See the [SDK documentation](/platform/resources/sdks/web-sdk/overview) for more information. Beta Feature # One-off Source: https://developer.hellgate.io/products/commerce/v2/api-reference/authentications_ci/one-off /products/commerce/v2/openapi.yaml post /authentications/one-off Request an authentication for a one-off payment. This API endpoint triggers a 3-D Secure (3DS) authentication without processing a payment. It is used to verify a customer's identity before completing a transaction or for regulatory compliance (e.g., Strong Customer Authentication - SCA). The customer must be on-session to complete the authentication. Commerce utilizes the action_requirement mechanism to request any necessary interaction with the customer. The interactions are supported with SDKs. See the [SDK documentation](/platform/resources/sdks/web-sdk/overview) for more information. Beta Feature # Get an authentication Source: https://developer.hellgate.io/products/commerce/v2/api-reference/authentications_data/get-an-authentication /products/commerce/v2/openapi.yaml get /authentications/{id} Return the details of an authentication referenced by its id Beta Feature # Get authentications Source: https://developer.hellgate.io/products/commerce/v2/api-reference/authentications_data/get-authentications /products/commerce/v2/openapi.yaml get /authentications Get a list of all the authentications for this account. The results are grouped into pages. The caller can specify how many customers per page shall be listed (maximum is 500). Beta Feature # Subsequent installment Source: https://developer.hellgate.io/products/commerce/v2/api-reference/authentications_mi/subsequent-installment /products/commerce/v2/openapi.yaml post /authentications/installment/{id}/subsequent Authenticate for a subsequent installment payment. This authentication is initiated by the requestor (3RI) and will not require interactions with customers; it can be run off-session. Beta Feature # Subsequent recurring Source: https://developer.hellgate.io/products/commerce/v2/api-reference/authentications_mi/subsequent-recurring /products/commerce/v2/openapi.yaml post /authentications/recurring/{id}/subsequent Authenticate for a subsequent recurring payment. This authentication is initiated by the requestor (3RI) and will not require interactions with customers; it can be run off-session. Beta Feature # Overview Source: https://developer.hellgate.io/products/commerce/v2/api-reference/automation/overview Generate reconciliation reports for imported tokens The Automation API provides tools for reconciling imported token data. ## Reconciliation Reconcile imported token data to ensure data integrity and track the migration process. To import payment methods from Stripe.com, see the [Stripe backup guide](/products/commerce/v2/guides/backup-stripe). # Forward card data Source: https://developer.hellgate.io/products/commerce/v2/api-reference/compliance_service/forward-card-data /products/commerce/v2/openapi.yaml post /forward This endpoint allows to securely forward cardholder data to a certified third-party provider. It will inject sensitive cardholder data, referenced by a Commerce token, into the request before forwarding it. Commerce requires certain HTTP headers for this feature. These headers are reserved and cannot be used by the caller: | Header | Required | Description | | ----------------- | -------- | ------------------------------------------------------------ | | `x-api-key` | Yes | Authentication of the request. | | `x-hellgate-token`| Yes | Identify the Commerce token to use for the request. | | `x-destination-url` | Yes | Define the target URL to which the request shall be forwarded. | | `x-hellgate-version` | No | Define the API version to use for the request. | Refer to the general documentation about [forwarding senstive data](/products/commerce/v2/guides/forwarding) for more information. # Overview Source: https://developer.hellgate.io/products/commerce/v2/api-reference/configuration/overview Configure and manage merchant accounts Configure and manage merchant accounts based on your chosen operating model. The merchant management capabilities adapt to support all four Commerce V2 operating models. The default model for single businesses. Your account operates as a standalone merchant processing its own transactions. * Single merchant configuration locked to your primary account * Cannot add sub-merchants (`POST /merchants` disabled) * Cannot delete the primary merchant (`DELETE /merchants/{id}` disabled) * Full access to all payment features for your own transactions Enable multiple sellers or service providers to process payments through your platform. * Add and manage multiple sub-merchants * Configure individual processing rules per merchant * Track transactions by merchant for split payments * Implement marketplace payment flows Platform model requires activation by your account manager. Grant controlled payment capabilities to third parties within your business ecosystem. * Onboard ecosystem partners as sub-merchants * Set transaction limits and permissions per partner * Maintain oversight of all ecosystem transactions * Common in mobility, delivery, and service networks Ecosystem model requires validation of your use case by your account manager. Full payment facilitation with comprehensive merchant management capabilities. * Advanced sub-merchant onboarding and underwriting * Risk management and compliance tools * Detailed merchant monitoring and reporting * Support for complex multi-party payment flows Managed Ecosystem requires special licensing and compliance review. For more detailed information about operating models, see the [Operating Models documentation](/products/commerce/v2/operating-models). # Delete credentials Source: https://developer.hellgate.io/products/commerce/v2/api-reference/credentials_management/delete-credentials /products/commerce/v2/openapi.yaml delete /credentials/{id} Remove a credentials from Commerce. This operation cannot be undone. # Get credential details Source: https://developer.hellgate.io/products/commerce/v2/api-reference/credentials_management/get-credential-details /products/commerce/v2/openapi.yaml get /credentials/{id} Get the detail information about stored credentials. # Get credentials Source: https://developer.hellgate.io/products/commerce/v2/api-reference/credentials_management/get-credentials /products/commerce/v2/openapi.yaml get /credentials Get a list of credentials on file which belong to this account. The results are grouped into pages. The caller can specify how many customers per page shall be listed (maximum is 500). # Overview Source: https://developer.hellgate.io/products/commerce/v2/api-reference/customer-data/overview Manage stored payment credentials for repeat customers Manage stored payment credentials for repeat customers across all operating models. Essential for subscription services, marketplaces, and ecosystems requiring seamless repeat transactions. ## Capabilities * **Credential storage**: Securely store customer payment methods for future use * **Cross-processor portability**: Use stored credentials with any connected processor * **Compliance management**: Automatic handling of card scheme mandates * **Customer control**: Enable customers to view and manage their stored payment methods ## Use Cases * Subscription services requiring recurring billing * Marketplaces with repeat customers * Ecosystems with partner payment facilitation * Card-on-file scenarios for unscheduled payments # Create a merchant Source: https://developer.hellgate.io/products/commerce/v2/api-reference/merchants/create-a-merchant /products/commerce/v2/openapi.yaml post /merchants Create a new merchant. Merchants represent legal entities which are interacting with the card networks. Onboarding merchants for an account is a prerequisite for creating network tokens. Depending on the setup of the account, this allows to render complex ecommerce ecosystem setups. # Delete a merchant Source: https://developer.hellgate.io/products/commerce/v2/api-reference/merchants/delete-a-merchant /products/commerce/v2/openapi.yaml delete /merchants/{id} Remove a merchant from Commerce. This operation cannot be undone. # Get merchant details Source: https://developer.hellgate.io/products/commerce/v2/api-reference/merchants/get-merchant-details /products/commerce/v2/openapi.yaml get /merchants/{id} Get the information of a merchant. # Get merchants Source: https://developer.hellgate.io/products/commerce/v2/api-reference/merchants/get-merchants /products/commerce/v2/openapi.yaml get /merchants Get a list of all merchants that belong to this account. The results are grouped into pages. The caller can specify how many customers per page shall be listed (maximum is 500). # Update merchant Source: https://developer.hellgate.io/products/commerce/v2/api-reference/merchants/update-merchant /products/commerce/v2/openapi.yaml patch /merchants/{id} Update the information of a merchant. # Forward payment-data Source: https://developer.hellgate.io/products/commerce/v2/api-reference/network_tokens/forward-payment-data /products/commerce/v2/openapi.yaml post /payment-data/{id}/forward This endpoint enables platform merchants to leverage payment-data bundles in their acquirer integration. The request is forwarded to the configured destination URL of your merchant settings. Commerce requires certain HTTP headers for this feature. These headers are reserved and cannot be used by the caller: | Header | Required? | Description | | ------ | --------- | ----------- | | `x-api-key` | Yes | Authentication of the request. | | `x-hellgate-version` | No | Define the API version to use for the request. | Refer to the general documentation about [forwarding senstive data](/products/commerce/v2/guides/forwarding) for more information. Gated Feature # Get payment-data Source: https://developer.hellgate.io/products/commerce/v2/api-reference/network_tokens/get-payment-data /products/commerce/v2/openapi.yaml get /tokens/{id}/payment-data List all payment-data bundles created for this Commerce Token. Gated Feature # Request payment-data Source: https://developer.hellgate.io/products/commerce/v2/api-reference/network_tokens/request-payment-data /products/commerce/v2/openapi.yaml post /tokens/{id}/payment-data Request a payment-data bundle for a Commerce token. The bundle can contain various elements including the TAVV cryptogram for a network token. This call supports the all operating models of Commerce (**single merchant** and **eco-systems**). For eco-systems you need to specify which merchant you want to request the crypotgram for. Gated Feature # Update a network token Source: https://developer.hellgate.io/products/commerce/v2/api-reference/network_tokens/update-a-network-token /products/commerce/v2/openapi.yaml post /tokens/{id}/provisions If the provisioning of a network token status is `failed` or `not_available` for any reason, this endpoint can be used to provision a network token. Gated Feature # Payment notification Source: https://developer.hellgate.io/products/commerce/v2/api-reference/payment-notification /products/commerce/v2/openapi.yaml webhook paymentNotification The notification is used to inform a listener about changes to a payment. The listener must respond with an HTTP status code 200 to acknowledge and accept the notification. If a different status code is returned, Commerce will attempt to resend the notification, following a retry policy until the notification is successfully acknowledged or the maximum retry limit is reached. # Token Import Report Source: https://developer.hellgate.io/products/commerce/v2/api-reference/reconciliation/token-import-report /products/commerce/v2/openapi.yaml get /reports/tokens This report lets you reconcile imported tokens with their source. The report contains all Commerce tokens of the current account. Each token carries the `business_key` it was imported with (for example the ID of the payment method at Stripe.com), if any. # Refund notification Source: https://developer.hellgate.io/products/commerce/v2/api-reference/refund-notification /products/commerce/v2/openapi.yaml webhook refundNotification The notification is used to inform a listener about changes to a refund. The listener must respond with an HTTP status code 200 to acknowledge and accept the notification. If a different status code is returned, Commerce will attempt to resend the notification, following a retry policy until the notification is successfully acknowledged or the maximum retry limit is reached. # List transactions Source: https://developer.hellgate.io/products/commerce/v2/api-reference/refunds_data/list-transactions /products/commerce/v2/openapi.yaml get /refunds/{id}/transactions Retrieves a paginated list of all transactions associated with a refund. The results are grouped into pages, and the caller can specify the number of transactions per page via a query parameter, with a maximum limit of 500 transactions per page. If no value is specified, a default page size will be used. # Token notification Source: https://developer.hellgate.io/products/commerce/v2/api-reference/token-notification /products/commerce/v2/openapi.yaml webhook tokenNotification This webhook notifies the integration server about changes in the token state. # Overview Source: https://developer.hellgate.io/products/commerce/v2/api-reference/tokens/overview Create and manage payment tokens with network token support Commerce Tokens are a powerful instrument to manage card holder data in a PCI DSS compliant way. Achieve PCI DSS compliance in almost no time using our certified managed compliance service. Transparently use the cardholder data across all connected processors to implement a non lock-in acquiring setup. For every card which is tokenized in Commerce, network-tokens can be automatically provisioned. This allows for more secure transactions, better conversion, and ultimately lower processing cost. ## Create Tokens Create and import Commerce tokens to securely store cardholder data while maintaining PCI DSS compliance. ## Token Management All cards stored with Commerce can be managed via the endpoints in this section. The CVC2 security code is only kept in an ephemeral cache for a few minutes. The API allows you to manage this resource with these endpoints: * Check if the CVC2 is still available * Request a new session to renew the CVC2 with our SDK * Consume the token after a successful authorization on an external processor ## Compliance Service Safely handle sensitive cardholder data while maintaining PCI DSS compliance across all operating models. Our compliance service acts as a secure proxy between your systems and payment processors. Key features: * **PCI DSS Level 1 certified**: Reduce your compliance scope significantly * **Data forwarding**: Securely transmit cardholder data to processors without touching your systems * **Format preservation**: Maintain data formats required by different processors ## Network Tokens Commerce supports Network Tokens with major card schemes. The lifecycle of network tokens is automatically managed. When activated, a network token is automatically provisioned and maintained through its life-cycle. The use of Network Tokens requires prior activation by your account manager. # Create token from FPAN Source: https://developer.hellgate.io/products/commerce/v2/api-reference/tokens_create/create-token-from-fpan /products/commerce/v2/openapi.yaml post /cde-import Import cardholder-data in exchange for a Commerce token. In order to use this endpoint you need to prove a certain level of compliance with PCI DSS with us. Reach out to your account manager for more information. Gated Feature # Request a token session Source: https://developer.hellgate.io/products/commerce/v2/api-reference/tokens_create/request-a-token-session /products/commerce/v2/openapi.yaml post /tokens/session Create a session to start Commerce Tokenization using our SDK. # Check CVC2 Source: https://developer.hellgate.io/products/commerce/v2/api-reference/tokens_management/check-cvc2 /products/commerce/v2/openapi.yaml get /tokens/{id}/security-code Get an indicator if security information for a stored Commerce token is still available. # Consume a token Source: https://developer.hellgate.io/products/commerce/v2/api-reference/tokens_management/consume-a-token /products/commerce/v2/openapi.yaml post /tokens/{id}/consume Tell Commerce after successful use of the token for an payment authorization, that the CVC2 can be removed from cache. # Delete a token Source: https://developer.hellgate.io/products/commerce/v2/api-reference/tokens_management/delete-a-token /products/commerce/v2/openapi.yaml delete /tokens/{id} Remove a Commerce token and all associated information (e.g. network tokens). This operation cannot be undone. # Download card art Source: https://developer.hellgate.io/products/commerce/v2/api-reference/tokens_management/download-card-art /products/commerce/v2/openapi.yaml get /tokens/card-art/{id} Return the card art image itself, ready to be rendered by a browser. The response body is the raw image and the `Content-Type` header reflects the stored asset. This is the endpoint that a token's `card_art_url` attribute points at. Use that URL as it is given to you — the `{id}` in the path is the ID of the card art, not of the token, so the URL cannot be constructed from a token ID. This endpoint takes no API key, so the URL can be used directly as the `src` of an image element in your storefront. The URL contains an unguessable identifier and the response carries the image only — no token attributes and no cardholder data. # Get card art Source: https://developer.hellgate.io/products/commerce/v2/api-reference/tokens_management/get-card-art /products/commerce/v2/openapi.yaml get /tokens/{id}/card-art Get the card art associated with the token, as a base64-encoded image. Card art is provided by the issuer through the network token. It is not available for every card. To embed the image directly in a browser instead, use the ready-made URL in the token's `card_art_url` attribute. # Get token details Source: https://developer.hellgate.io/products/commerce/v2/api-reference/tokens_management/get-token-details /products/commerce/v2/openapi.yaml get /tokens/{id} Get a Commerce token by its identifer. # Get tokens Source: https://developer.hellgate.io/products/commerce/v2/api-reference/tokens_management/get-tokens /products/commerce/v2/openapi.yaml get /tokens Get a list of all Commerce tokens that belong to this account. The results are grouped into pages. The caller can specify how many customers per page shall be listed (maximum is 500). # Refresh CVC2 Source: https://developer.hellgate.io/products/commerce/v2/api-reference/tokens_management/refresh-cvc2 /products/commerce/v2/openapi.yaml post /tokens/{id}/refresh-session Create a session to update the Security Code from a Commerce Token using our SDK. # Refresh token metadata Source: https://developer.hellgate.io/products/commerce/v2/api-reference/tokens_management/refresh-token-metadata /products/commerce/v2/openapi.yaml post /tokens/{id}/refresh-metadata Re-run the issuer metadata inquiry for a Commerce token and return the updated token. Issuer metadata is resolved once when the token is created. Use this endpoint when you need the token re-checked against the current issuer data — for example after a portfolio migration on the issuer side. The inquiry can update `bank_name`, `segment`, `type`, `currency_code`, `country_code`, `issuer_identification_number` and `scheme`. Issuer metadata is only resolved if metadata inquiries are active for your account. If the inquiry does not return data, the card scheme is derived from the IIN and the remaining issuer fields keep their previous values. # Commerce v2 Updates Source: https://developer.hellgate.io/products/commerce/v2/changelog New releases and improvements of Commerce v2. ## Bulk Token Import Removed The import automation service that pulled saved cards from your Stripe.com account into Commerce is removed. The `/tokens/imports` endpoints — create, status, abort, and failures — no longer exist, `import_job.*` webhooks are no longer sent, and tokens and the tokens report no longer carry `import_provider`. A `metadata.import_id` sent on token creation is ignored. Backing up individual payment methods through Stripe's forward API to `/cde-import` keeps working, but only when CDE import is enabled for your account, which requires at least SAQ D PCI DSS compliance. A request no longer qualifies just because it comes from Stripe. See [Back Up Stripe.com Tokens](/products/commerce/v2/guides/backup-stripe). ## Improvements and bugfixes * Token webhooks always include the `payment_account_reference` key when scheme references are enabled for your account, `null` until the card scheme reports a PAR. The webhook payload now matches the token resource. * A token never changes without its `token.updated` or `token.deleted` webhook. If the webhook cannot be queued, the token change is rolled back with it instead of being kept without an event. ## Masked Network Token Number and PAR on Tokens Tokens now show the network token number (TPAN) in masked form, so you can match what your processor reports against a Commerce token. The [token resource](/products/commerce/v2/api-reference/tokens_management/get-token-details) carries `masked_network_token_number` and `network_token_last_four` next to the masked card number, and `payment_account_reference` when scheme references are enabled for your account. The same fields are part of the `token.created` and `token.updated` webhook payloads; the digits normally arrive with the first `token.updated` after provisioning. [List tokens](/products/commerce/v2/api-reference/tokens_management/get-tokens) accepts a `network_token_last_four` filter, mirroring `account_number_last_four`, which is now documented as well. The digits are filled in as the schemes report them: at provisioning for new tokens, on the next payment-data request or through a backfill run by Hellgate for tokens provisioned earlier. Only the masked digits leave the card data environment. See [Network Tokens](/products/commerce/v2/tokens/network#identifying-a-network-token). ## Deleted Network Tokens Show as Deleted When a network token is deleted in Guardian, the Commerce token that holds it now changes its `network_token_status` to `deleted` and you receive a `token.updated` webhook. This covers deletions reported by the card scheme as well as deletions made through the Guardian API. Until now only scheme-side deletions were reflected. A token deleted through the Guardian API kept its previous status in Commerce, so it looked usable although Guardian would no longer serve payment data for it. A deleted network token cannot be reactivated. ## Polymorphic Bodies Specified as Discriminated Unions The request and response bodies that accept several shapes — payment `source`, authentication `source`, payment splits, and the card payment source and credential — now pin their `type` to a single value per variant, so the specification states exactly which shape each `type` selects. Accepted requests are unchanged. This fixes a real ambiguity rather than only a presentational one. Because the split variants left `type` unconstrained, a commission split that also carried `merchant_id` satisfied both the seller and the commission shape at once, which made it fail a strict `exactly one of` check even though the API accepts it. Validating against our specification now gives the same answer as the API. Each variant also carries a name, so the reference documentation labels the choices — "Checkout", "Token", "Credential", "Seller", "Commission" — instead of "Option 1" and "Option 2". ## Corrected Status Code for Token Sessions [Request a token session](/products/commerce/v2/api-reference/tokens_create/request-a-token-session) is now documented as returning **201 Created**. The endpoint has always responded 201 — the API reference said 200, which was wrong. Nothing about the endpoint changed, and the response body is the same `session_id` it always was. If your integration asserts on the status code, assert 201 here. The related [Refresh CVC2](/products/commerce/v2/api-reference/tokens_management/refresh-cvc2) endpoint returns a plain 200 and is unaffected. ## Card Art and Metadata Refresh Are Documented Three endpoints that Commerce v2 already served are now part of the API reference. Their behaviour is unchanged — only the documentation is new. * **[Get card art](/products/commerce/v2/api-reference/tokens_management/get-card-art)** returns the issuer's card art for a token as a base64-encoded image, in the same shape Guardian uses for network tokens. * **[Download card art](/products/commerce/v2/api-reference/tokens_management/download-card-art)** returns the image itself. This is what the `card_art_url` attribute on a token points at. It takes no API key, so the URL can be rendered directly by a browser. Use the URL as it is given to you: the ID in the path identifies the card art, not the token. * **[Refresh token metadata](/products/commerce/v2/api-reference/tokens_management/refresh-token-metadata)** re-runs the issuer inquiry for a token and returns the updated token, which can change `bank_name`, `segment`, `type`, `currency_code`, `country_code`, `issuer_identification_number` and `scheme`. This endpoint was announced in May but never had a reference page — it now does. `card_art_url` is also documented as nullable, which it always was — it is `null` when the issuer provides no card art. ## Raw Card Numbers Are Rejected Everywhere Commerce v2 is not a card data environment, and now enforces it: any API request body containing a raw card number (PAN) is rejected, on every endpoint. Card data belongs in your PCI-compliant tokenization flows — this guard keeps it from slipping into ordinary API calls by accident. Merchant identifier fields are exempt, since an acquirer-assigned ID can be indistinguishable from a card number. ## Improvements and bugfixes * Token responses handle a missing card art from Guardian gracefully instead of failing. * Accounts with the GPayments 3DS provider disabled now degrade gracefully instead of raising errors. * Updated dependencies to clear security advisories. ## Improvements and bugfixes * Bulk card imports no longer emit duplicate `token.created` events. * Tokens created through PCI tokenization now pass their TTL through to Guardian, keeping token expiry consistent end to end. * Unexpected errors from the 3DS authentication provider now surface as readable failures instead of opaque errors. * Client headers are stripped before forwarding requests to Guardian, fixing intermittent `504` responses. * The Test tier network simulation now returns the dynamic CVV for American Express cards. * Request logs now mask API keys passed as query parameters. ## Improvements and bugfixes * Network token forwarding is more resilient: Commerce now injects the destination URL from your KYC configuration when the client omits it. * Ephemeral session cards that are never saved no longer trigger network token creation, and an expired Guardian token is treated as already deleted when you tear a session down. * The 3DS admin path handles unexpected merchant responses gracefully instead of failing the request. ## PCI Control Adjustment Refined how merchant network token data is decrypted in composition with Guardian, keeping the data path aligned with our PCI DSS controls. ## Composing Commerce v2 with Guardian Advancing our [Composable Payment Architecture](/platform/cpa), Commerce v2 now composes natively with Guardian for network token data, cryptograms, and issuer metadata. Opt an account in with the new `guardian_cpa_mode` and `issuer_metadata_enabled` settings (alongside its `contract_id`): Commerce resolves cryptograms by reference through Guardian and surfaces each token's `expires_at` with the resolved token data. A new `POST /tokens/{id}/refresh-metadata` endpoint lets you refresh issuer metadata on demand instead of waiting for the next payment. ## User MFA Recovery A new endpoint revokes a user's multi-factor authentication, so you can reset MFA for a locked-out operator instead of escalating to support. ## Improvements and bugfixes * Token responses now include the `currency` field. * Clearer errors when retrieving payment data, plus a precise message when a `contract_id` exceeds the allowed length instead of an opaque bad request. * Unsupported network token creation now returns a readable error message. * 3DS requests now return a clean `502` when the upstream authentication provider's TLS handshake fails, instead of an ambiguous error. * Issuer metadata falls back to card scheme data when a lookup is disabled or unavailable, so scheme information is always populated. * Hardened data-request logging to never persist `x-api-key` or `authorization` headers, and restored a missing Content-Security-Policy header. * Updated dependencies to clear a security advisory. ## Allowed Card Schemes per Account You can now restrict which card schemes are accepted on a given account or session. A new endpoint lets you configure the allow-list, sessions pick it up automatically, and the Web SDK validates it client-side before the cardholder even hits submit. Great for merchants with acquirer- or region-specific scheme constraints. ## Improvements and bugfixes * Sub-merchants can now only see their own payment data — tightened scoping across reads. * Hardened against several `500` paths: invalid UUIDs in API-logs queries, missing required fields on authentication installments, Guardian authentication-failed responses, and oversized AOC document links. * Network token decline reports and scheme-percentage views render consistently for accounts with allowed-schemes configured. ## Network Token Statistics and Details You now have deeper visibility into your network tokenization activity. New reporting endpoints let you export BIN statistics as CSV, review declined network tokenizations, and access a token activity summary — giving you the data you need to monitor performance and troubleshoot issues. Token filtering has also been extended to support lookup by token ID or last four digits, making it easier to find specific tokens. ## Improvements and bugfixes * Webhooks no longer fire when there are no changes to a token. * Fixed a date format issue in 3DS session context. * Ecosystem accounts and sub-merchants can now execute proxy forwards. * Improved error handling for Guardian serialization errors and unknown tokens. ## Introduction of Generic Tokens With the rollout of TMS support on Guardian, Commerce v2 now supports this tokenization backend as well. ## Bugfix * Improved the SDK session protocol against unexpected data and up-stream errors. ## Bugfixes * Improved session error handling on CDE import. * Handle card-art corner cases for some issuers. * Handle BIN data gracefully when missing some attributes. ## Deterministic TPANs We added support for deterministic TPANs (Token PAN) on the Network Token Sandbox. This is a great help for developers to test their integration with consistent data. ## Payment Splits are generally available We rolled out payment splits for all our customers on the platform operating model. This allows you to split the payment amount between multiple accounts. ## Improvements and bugfixes * Better error messages on invalid payloads for session completion (specifically encrypted CHD payloads) * Wider support of metadata in requests. * Fixed issuer metadata retrieval for cards without default currency. * Support more exotic cardart data from issuers. ## Templating * We added a new filter last(n) to the forwarding templates. ## Improvements and bugfixes * We optimized the performance of our authentication subsystem, which makes literally every API interaction just faster. * Apply correct scoping of sub-merchant level API keys. * Fix issues with certain compliance proxy forwards. ## Bring your own Guardian Mix and match - Use your own dedicated Guardian in Commerce setups. ## Bugfix * Fixed the replenishment of encryption keys for account owners on eco-system operating model. ## Hello CPA 👋 July has been nothing short of monumental for the Hellgate® engineering team – and we’re beyond thrilled to finally unveil what we’ve been building behind the scenes. Say hello to the next generation of payment innovation: [The Composable Payment Architecture](/platform/cpa). ### Built for Seamless Integration With CPA, we’re rewriting the rules of payment technology. Hellgate® is no longer just a powerful platform – it’s a composable, integratable powerhouse. We're opening up our legendary "secret sauce" to empower developers, partners, and innovators everywhere. Think modular. Think agile. Think tailor-made payments infrastructure. We're rolling out these new services step by step – purpose-built for scalability, flexibility, and collaboration. It's everything you love about Hellgate, now available piece by piece for maximum integration and innovation. ### Hellgate Evolves: Introducing Hellgate Commerce We’re all-in on the orchestration journey. Say hello to Commerce – your full-stack command center for modern payments. We’re pushing the limits of productivity, control, and performance in every update. The name is new, but our commitment to excellence remains stronger than ever. ### Meet Guardian CPA: Power Meets Privacy Kicking off our Early Access Program is Guardian – the cornerstone of CPA. Guardian delivers a fully managed PCI DSS-compliant cardholder data vault, so you can store sensitive data with confidence and speed. Need more control? We’ve got you covered with private Guardian deployments, bringing top-tier data privacy and security directly to your Commerce environment. # Create backend Source: https://developer.hellgate.io/products/link/api-reference/backends/create-backend /products/link/openapi.yaml post /api/admin/backends # Delete backend Source: https://developer.hellgate.io/products/link/api-reference/backends/delete-backend /products/link/openapi.yaml delete /api/admin/backends/{id} # Get backend Source: https://developer.hellgate.io/products/link/api-reference/backends/get-backend /products/link/openapi.yaml get /api/admin/backends/{id} # List backends Source: https://developer.hellgate.io/products/link/api-reference/backends/list-backends /products/link/openapi.yaml get /api/admin/backends # Rotate credentials Source: https://developer.hellgate.io/products/link/api-reference/backends/rotate-credentials /products/link/openapi.yaml post /api/admin/backends/{id}/rotate-credentials # Update backend Source: https://developer.hellgate.io/products/link/api-reference/backends/update-backend /products/link/openapi.yaml put /api/admin/backends/{id} # Getting Started Source: https://developer.hellgate.io/products/link/api-reference/getting-started Administer Link protocols and backends over the REST API. The Link admin API manages the [protocols](/products/link/concepts/protocols) and [backends](/products/link/concepts/backends) on your instance. It is a REST API, and every request carries a bearer JWT. The documented endpoints on this reference, generated from the same OpenAPI specification. Duplicate the **Dedicated instance — template** environment and set `instance` and `env`. The Authorization tab is pre-configured for the OAuth2 client-credentials grant, so add `clientId`, `clientSecret`, `audiences` and `scopes` to the environment and hit **Get New Access Token**. Both `audiences` and `scopes` are space-delimited lists — name every instance the token may call. See [Authentication](/platform/authentication). ## Base URL Reach your instance at: ``` https://{instance}.eu1.on-hellgate.cloud ``` `{instance}` is your unique instance slug and `eu1` is the current environment — see [Plans & Access](/products/link/plans-and-access). ## Authentication Every request requires a bearer token holding the scope for that operation: ```http theme={null} Authorization: Bearer ``` See [Authentication](/products/link/authentication) for the token flow and the full scope list. ## Editions What you can administer depends on your [edition](/products/link/plans-and-access#editions): * **Bundled** — manage the backends that serve your connected Specter instance. * **Standalone** — additionally run any Hellgate-curated protocol and import **your own** protocols. These operations are marked Standalone. If you've opted for Hellgate to operate backends on your behalf, those managed backends are changed with the `admin:managed-backends:write` scope. ## Next steps Import, inspect, and manage protocols. Configure and operate the providers behind a protocol. # Invoke protocol action Source: https://developer.hellgate.io/products/link/api-reference/invoke/invoke-protocol-action /products/link/openapi.yaml post /api/invoke/{protocol}/{action} Executes a protocol action against a backend. The HTTP method must match the action's declared method. The request and response bodies are **defined by the invoked protocol**, not by this API — retrieve the exact shapes from the protocol's generated OpenAPI (`GET /api/admin/protocols/{id}/openapi`). # Delete protocol Source: https://developer.hellgate.io/products/link/api-reference/protocols/delete-protocol /products/link/openapi.yaml delete /api/admin/protocols/{id} Removes a protocol. Fails if backends reference it. # Get generated OpenAPI spec Source: https://developer.hellgate.io/products/link/api-reference/protocols/get-generated-openapi-spec /products/link/openapi.yaml get /api/admin/protocols/{id}/openapi Returns a standard OpenAPI 3.1 spec generated from the protocol. # Get protocol document Source: https://developer.hellgate.io/products/link/api-reference/protocols/get-protocol-document /products/link/openapi.yaml get /api/admin/protocols/{id} Returns the full protocol specification document. # Import by spec Source: https://developer.hellgate.io/products/link/api-reference/protocols/import-by-spec /products/link/openapi.yaml post /api/admin/protocols Standalone Importing protocols requires a Standalone instance. A Bundled instance is locked to serving its Specter instance's backends. Creates a protocol by posting the spec document directly. # Import by URL Source: https://developer.hellgate.io/products/link/api-reference/protocols/import-by-url /products/link/openapi.yaml post /api/admin/protocols/import Standalone Importing protocols requires a Standalone instance. A Bundled instance is locked to serving its Specter instance's backends. Fetches a protocol document from a URL, validates, and stores it. # List protocols Source: https://developer.hellgate.io/products/link/api-reference/protocols/list-protocols /products/link/openapi.yaml get /api/admin/protocols Returns a paginated list of registered protocols. # Authentication Source: https://developer.hellgate.io/products/link/authentication Authenticate to your Link instance with a bearer JWT, and scope each token to the operations it needs. Every Link API request — admin and runtime — must carry a bearer **JSON Web Token (JWT)** in the `Authorization` header: ```http theme={null} Authorization: Bearer ``` ## Obtaining a token Link runs on Hellgate-managed infrastructure and authenticates with the platform OAuth2 flow. Exchange your client credentials for an access token, then send it as a bearer token. Your instance's audience is its instance name — the `{instance}` slug from your base URL — and you request it together with the scopes your client needs: ``` audience=my-link-instance ``` When Link is invoked transitively by another service — for example [Specter](/products/specter/overview) composing with Link — the token must name **every** instance in the composition and carry the union of their scopes (for example, `audience=my-specter-instance my-link-instance`). See [Composition](/platform/authentication#composition) for details. See [Platform Authentication](/platform/authentication) for the token endpoint, audiences, and the client-credentials request. ## Scopes Each endpoint requires a specific scope. Issue every token with the **least privilege** it needs — runtime clients should hold only `invoke:execute`, while configuration and operations belong to separate admin clients. | Scope | Grants | | - | - | | `invoke:execute` | Invoke protocol actions (`/api/invoke/{protocol}/{action}`) | | `admin:protocols:read` | Read protocols, requirements, and generated specs | | `admin:protocols:write` | Create, import, and delete protocols | | `admin:backends:read` | Read backends | | `admin:backends:write` | Create, update, delete, and rotate credentials on backends | | `admin:managed-backends:write` | Manage Hellgate-operated (managed) backends and their secrets | | `admin:keys:read` | Read message-level-encryption keys | | `admin:keys:write` | Create, update, delete, and rotate encryption keys | | `admin:executions:read` | Read execution audit logs | Runtime and admin scopes serve different audiences. Grant `invoke:execute` to your payment or decisioning integration; reserve the `admin:*` scopes for operator and back-office clients that configure protocols, backends, and keys. ## Next steps Use your token to import a protocol, register a backend, and invoke an action. # Link Updates Source: https://developer.hellgate.io/products/link/changelog New releases and improvements of Link. ## Execution Log Entries Expire After 30 Days Link now keeps execution log entries for a finite window — 30 days by default, set per instance — and deletes them in full once it has passed. Until now an instance without an explicit window kept every entry, including the raw provider response, indefinitely. The change aligns Link with the defined retention period PCI DSS requires for stored account data and with how Specter already ages out provider payloads. What to check on your side: * **Fetching an expired execution returns `404`.** If you look up executions by ID after the fact — for reconciliation or dispute handling — do so within the window, or export the entries you need before it closes. * **The window is fixed per instance.** Talk to us if your instance needs a different period; an unlimited window is no longer available. ## Provider Calls Reach the Provider You Configured Link verifies a backend's address before it connects and holds the call to that verified address for its whole duration, closing a class of network attack in which a call meant for your provider is diverted to another destination. Nothing changes in how you configure a backend, and no action is needed for this protection to apply. Two points are worth checking against backends you already run: * **A backend needs its final address.** Link does not follow redirects. A provider endpoint that answers with a `3xx` should be configured with the address it redirects to. * **Providers that verify our signature from behind a path prefix.** If a backend's base URL includes a path prefix and your provider checks the signature Link sends, verify it against a live request once — the signature now covers the full address the request is actually sent to. ## Improvements and Bugfixes * Editing a backend's authentication pipeline in the console saves what you entered. Header changes were discarded on save, and saving could drop settings that had been configured through the API — a provider could be left authenticating differently than intended. Both are fixed. * A long request body or response mapping can be edited all the way to the end. The JSON editor was cut off at a fixed height, so a body past a certain length could not be scrolled to or corrected without going through the API; it now fills the dialog and scrolls. ## Run Link Within SAQ A and SAQ A-EP Scope Link now runs on instances assessed at SAQ A or SAQ A-EP — you no longer need a SAQ-D environment to invoke protocols. Below SAQ-D, Link enforces the boundary that keeps your assessment scope small: invoke request bodies carrying a raw card number (PAN) are rejected with a `422` validation error. At SAQ-D and above, your protocol schema decides which attributes legitimately carry card data. Admin routes are exempt at every level, so backend mappings and protocol specs can keep example card numbers. The platform test-card policy applies on every instance: on the Test tier, only known test cards pass; on the Production tier, test cards are rejected while real card numbers flow as your protocol allows. ## Link Is Live Link — the protocol-abstraction runtime of the [Composable Payment Architecture](/platform/cpa) — is now generally available on the Test and Production tiers. Define a versioned API contract (a [protocol](/products/link/concepts/protocols)) once and bind it to any provider through declarative [backends](/products/link/concepts/backends), so a new provider integration becomes configuration instead of code. It is the connectivity layer [Commerce](/products/commerce/v2/overview) routes through to reach acquirers and PSPs, and that [Specter](/products/specter/overview) reaches through to call external risk engines. Start with the [overview](/products/link/overview), the [quickstart](/products/link/quickstart), and the [API reference](/products/link/api-reference/getting-started). # Backends Source: https://developer.hellgate.io/products/link/concepts/backends A backend binds a protocol to a real provider: its host, credentials, authentication, and the declarative mapping rules that translate the contract into the provider's wire format. A **backend** binds a [protocol](/products/link/concepts/protocols) to a real external provider. It supplies the provider's host and credentials, an authentication strategy, and the **mapping rules** that translate each protocol action into the provider's wire format — and the provider's response back into the protocol's shape. One protocol can have many backends. That is the core of Link: define the contract once, then point it at as many providers as you need, and switch or combine them without changing the caller. In a Specter integration, a Link backend *is* the external risk engine Specter calls. Specter reaches it over the `specter-v1` protocol — see [Specter backends](/products/specter/concepts/backends). ## Anatomy ```json theme={null} { "id": "acme-risk", "name": "Acme Risk", "protocol": "https://developer.hellgate.io/protocols/specter/v1", "host": "api.acme.example", "enabled": true, "credentials": { "token": "secret_key_test_..." }, "auth_pipeline": { "source_type": "inline", "credential_type": "bearer" }, "connections": { "assess.pan": { "...": "..." } } } ``` * **`id`** — a short identifier you choose, used to address the backend (for example when disambiguating an invocation). * **`protocol`** — the protocol's `$id` URL; must match an imported protocol. * **`host`** — the provider's hostname (no scheme, no path). * **`credentials`** — consumed by the authentication pipeline; encrypted at rest. * **`auth_pipeline`** — how outbound requests are authenticated (see below). * **`connections`** — a map from action (or `action.variant`) to its mapping configuration. Backend hosts are checked by an SSRF guard when a backend is created or updated (imported protocol URLs are checked the same way). The URL must use `https`, the host must resolve, and **every** resolved IPv4 and IPv6 address must fall outside the reserved ranges — private, loopback, link-local, multicast, carrier-grade NAT, and IPv6 tunneling prefixes (Teredo, 6to4, NAT64). The check runs at configuration time, not on each invocation. ## Authentication strategies The `auth_pipeline` determines how Link authenticates outbound calls to the provider. A non-empty pipeline requires a `source_type` (`inline` or `vault` — where the credentials come from) and a `credential_type`: | `credential_type` | Behavior | | - | - | | *(omit the pipeline, or `{}`)* | No authentication added. | | `basic` | HTTP Basic, from `username` / `password` credentials. | | `bearer` | `Authorization: Bearer `. Set `token_prefix` to use a different scheme word (for example `token`). | | `hmac_sha256` | HMAC-SHA256 request signing, including a `Digest` over the request body. | ```json theme={null} "auth_pipeline": { "source_type": "inline", "credential_type": "bearer", "token_prefix": "token" } ``` Auth-pipeline headers (HMAC `Digest`, etc.) take precedence over mapped headers on conflict, so signing guarantees are never overwritten. ### Credentials and secrets Credentials are held either **inline** (encrypted at rest in the backend record) or pulled from a **secrets store** through a pointer, so the secret value itself never lives in the backend document — Link fetches it at request time. Inline credentials can be rotated in place with the rotate-credentials endpoint, without recreating the backend. ## Provisioning: self vs. managed A backend declares a provisioning mode: * **`self`** (default) — you own and manage the backend and its credentials. * **`managed`** — the backend is provisioned and operated by Hellgate on your behalf (an optional service). Its create, update, and delete operations require the additional `admin:managed-backends:write` scope on top of `admin:backends:write`. Credentials are never returned by the admin read endpoints, regardless of provisioning mode. ## Mapping Mapping is **declarative**: adapter logic lives in versioned templates, not in code. Each connection has a `request_mapping` and a `response_mapping`. ### Template grammar Mapping leaves are plain JSON values. Strings carry a **mustache-style grammar** — zero or more `{{ expr }}` holes inside a literal string — where `expr` is a scoped path followed by an optional chain of filters: ```json theme={null} "pan": "{{ $.request.body.first_six | required }}XXXXXX{{ $.request.body.last_four | required }}" ``` Paths are **scoped** to a root: | Root | Resolves to | Valid in | | - | - | - | | `$.request.body.*` | The caller's invoke request body | Request and response mappings | | `$.request.header.*` | The caller's request headers (downcased) | Request and response mappings | | `$.response.body.*` | The provider's response body | Response mappings only | | `$.response.header.*` | The provider's response headers | Response mappings only | Filters chain with `|`. Common filters include `required` (abort the call if the value is missing), `default()` (fall back to another value), `omit_if_null` (drop the key when null), `map({ FROM: TO })` (translate enum values), `to_int`, and `first` / `last` / `prefix` for string slicing. ### Request mapping `request_mapping` mirrors the provider's expected request. It has an optional `body` (a nested object matching the provider's shape) and `headers` (a flat map of header name to template): ```json theme={null} "request_mapping": { "body": { "amount": "{{ $.request.body.transaction.amount | required }}", "currency": "{{ $.request.body.transaction.currency | required }}" }, "headers": { "x-correlation-id": "{{ $.request.header.x-correlation-id | omit_if_null }}" } } ``` ### Response mapping `response_mapping` is keyed by the provider's HTTP status (`200`, `4xx`, `default`, …). Each entry has a `return` (the HTTP status Link returns to the caller) and a `body` template that produces the protocol-shaped result: ```json theme={null} "response_mapping": { "200": { "return": "200", "body": { "type": "enum", "value": "{{ $.response.body.status | map({ ACCEPTED: ALLOW, REJECTED: BLOCK }) | required }}", "backend_reference": "{{ $.response.body.id | omit_if_null }}" } }, "4xx": { "return": "422", "body": { "type": "error", "source": "backend", "code": "{{ $.response.body.error | omit_if_null }}" } } } ``` Response `body` templates are validated at backend-write time against the protocol's response schema for the given `return` code — missing required keys, unknown keys, and unmatched union branches are rejected before the backend goes live. ### Connection keys A connection key is either the bare action name (`assess`) or `action.variant` (`assess.pan`) when the action has a discriminator. Link validates connections against the imported protocol and rejects a backend that misses a required action or variant. ## Mocks Instead of live mapping, a connection can declare **mock responses** — match conditions plus canned responses. When present, invocations are matched against the mocks and served without any external call. Mocks are ideal for local development, tests, and protocol fixtures. ```json theme={null} "resolve": { "mocks": [ { "match": {}, "respond": { "type": "enum", "value": "ALLOW" } } ] } ``` ## Message-level encryption Some providers require every request and response body to be encrypted on top of TLS (JWE). A backend can carry an `encryption` block that makes Link encrypt outbound bodies and decrypt inbound ones transparently, so callers keep sending and receiving plain JSON. Encryption keys are managed separately and support rotation. ## Selecting a backend When several enabled backends implement the same action and variant, the caller disambiguates with a `backend` query parameter on invocation. See [Invocation](/products/link/concepts/invocation#selecting-a-backend). ## Next steps Call an action, match the method, select a backend, and handle errors. The contract a backend implements. # Invocation Source: https://developer.hellgate.io/products/link/concepts/invocation Calling a protocol action: how Link selects a backend, runs the mapping, and returns a protocol-shaped result or error. **Invocation** is calling a protocol action. Link validates the request, selects a matching [backend](/products/link/concepts/backends), runs the request mapping, dispatches the provider call, maps the response back to the [protocol's](/products/link/concepts/protocols) shape, and records an [execution](#execution-log). ``` {METHOD} /api/invoke/{protocol}/{action} ``` `{protocol}` is the protocol's local ID and `{action}` is the action name. The call requires the `invoke:execute` scope. ```bash theme={null} curl -X POST https://{instance}.eu1.on-hellgate.cloud/api/invoke/specter-v1/assess \ -H "Authorization: Bearer $TOKEN" \ -H "Content-Type: application/json" \ -d '{ "credential": { "type": "pan", "pan": { "value": "4111111111111111" } }, "transaction": { "amount": 4999, "currency": "USD" } }' ``` ## The flow ```mermaid theme={null} sequenceDiagram participant C as Caller participant L as Link participant P as Provider C->>L: {METHOD} /api/invoke/{protocol}/{action} Note over L: Validate request · resolve variant · select backend L->>P: Mapped, provider-native request P-->>L: Provider response Note over L: Map response · write execution log L-->>C: Protocol-shaped result ``` 1. **Validate** the request against the action's schema. 2. **Resolve the variant** from the discriminator field, if the action is polymorphic. 3. **Select the backend** that implements the action and variant. 4. **Map and dispatch** the request to the provider. 5. **Map the response** back to the protocol shape and **log** the execution. ## Method matching The HTTP method you invoke with must match the method the action declares in the protocol. A mismatch returns `405 METHOD_NOT_ALLOWED` — it is not routed to a different action. ## Selecting a backend If exactly one enabled backend implements the action and variant, Link uses it. If **several** match, Link returns `409 CONFLICT`; disambiguate with the `backend` query parameter: ```bash theme={null} curl -X POST "https://{instance}.eu1.on-hellgate.cloud/api/invoke/specter-v1/assess?backend=acme-risk" ... ``` ## Results A successful invocation returns the **protocol-shaped** result. Its shape is defined entirely by the protocol — for the `specter-v1` `assess` action, that is an enum outcome: ```json theme={null} { "type": "enum", "value": "ALLOW", "backend_reference": "dec-xyz" } ``` Two response headers accompany every result: * **`x-link-execution`** — the execution ID; use it to fetch the [audit entry](#execution-log). * **`server-timing`** — total and external-call timing, in milliseconds. ### Mapping errors When the provider call completes but mapping cannot produce a valid result, Link returns a protocol **error** result rather than failing silently. The `source` field tells you where it originated: ```json theme={null} { "type": "error", "source": "mapping", "code": "MISSING_REQUIRED_FIELD", "message": "..." } ``` `source` tells you where the error originated: `backend` (the provider returned an error you mapped through), `mapping` (a `required` leaf could not resolve), `transport` (the provider was unreachable or timed out), `encryption` (message-level encryption failed), or `mock` (a configured mock returned an error). Transport and encryption failures carry HTTP `502`; the rest use the status from the response mapping. ## Error codes | Status | Classifier | Cause | | - | - | - | | `401` | `UNAUTHORIZED` | Missing, expired, or malformed bearer token | | `403` | `FORBIDDEN` | Token lacks the `invoke:execute` scope | | `404` | `NOT_FOUND` | No enabled backend implements this action ("No backend implements this action") | | `404` | `NOT_FOUND` | The discriminator selected a variant no backend implements ("No backend implements this variant") | | `405` | `METHOD_NOT_ALLOWED` | The HTTP method does not match the action's declared method | | `409` | `CONFLICT` | Multiple backends match — pass `?backend=` | | `422` | `VALIDATION_ERROR` | The request failed schema validation — see `validation_errors` in the body | | `422` | `BACKEND_DISABLED` | The only matching backend is disabled | ## Execution log Every invocation — success or failure — is logged asynchronously. Fetch an entry by ID with the `admin:executions:read` scope: ```bash theme={null} curl -H "Authorization: Bearer $TOKEN" \ https://{instance}.eu1.on-hellgate.cloud/api/admin/executions/ ``` The entry contains the mapped protocol result, the raw provider response, timing, the backend that handled the call, and any error detail — so you can verify a round trip without provider-side tooling. ### Retention Your instance configures a retention window, in days, for the execution log; the default is 30 days. Once the window has passed, the entry is deleted in full — mapped result, raw provider response, timing, and error detail alike — and fetching it returns `404 NOT_FOUND`. Unlike a Specter decision, nothing of an expired execution survives, so export entries you need for long-term audit before the window closes. ## Next steps Configure the mapping that shapes each request and response. Run the full flow end to end. # Protocol schema Source: https://developer.hellgate.io/products/link/concepts/protocol-schema The Link protocol meta-schema — author your own protocols on a Standalone instance. Standalone On a [Standalone](/products/link/plans-and-access) instance you can author and import **your own** protocols. A protocol is a JSON document validated against the **Link protocol meta-schema** (`link-protocol-v1`) at import time; a document that does not conform is rejected. Custom protocols require a **Standalone** instance. A [Bundled](/products/link/plans-and-access) instance is locked to serving its Specter instance's backends. See [Protocols](/products/link/concepts/protocols) for the concept. The meta-schema is JSON Schema (draft 2020-12) and is served by every instance: ```bash theme={null} curl https://developer.hellgate.io/schemas/link-protocol/v1 ``` ## Top-level structure A protocol document has these fields (`additionalProperties` is `false` — unknown top-level keys are rejected): | Field | Required | Description | | - | - | - | | `$schema` | yes | Reference to the meta-schema: `https://developer.hellgate.io/schemas/link-protocol/v1` | | `$id` | yes | Canonical URI identifying this protocol. Backends reference the protocol by this URI; it is the join key. | | `name` | yes | Human-readable protocol name. | | `version` | yes | Version identifier (a string, for example `1.0.0`). | | `actions` | yes | Map of action name → [Action](#actions). At least one action is required. | | `components` | no | Reusable schemas referenced via `$ref` (see [Components](#components)). | ## Actions `actions` is keyed by action name. Names may use **dot notation** to namespace related operations (for example `notify.chargeback`). Each action is an object (`additionalProperties: false`): | Field | Required | Description | | - | - | - | | `method` | yes | HTTP method: `POST`, `GET`, `PUT`, `PATCH`, or `DELETE`. Callers must invoke with this method. | | `description` | yes | Human-readable description of the action. | | `request` | yes | The input definition — [monomorphic or discriminated](#requests). | | `responses` | yes | Output definitions keyed by HTTP status (see [Responses](#responses)). | | `required` | no | Whether backends **must** implement this action. Defaults to `false`. | ## Requests `request` is one of two shapes. ### Monomorphic — a single schema The request is a single [JSON Schema](#supported-json-schema-keywords): ```json theme={null} "request": { "type": "object", "properties": { "transaction": { "$ref": "#/components/schemas/Transaction" } }, "required": ["transaction"] } ``` ### Discriminated — a polymorphic union A discriminator field selects a named **variant**. Each variant is a JSON Schema: | Field | Required | Description | | - | - | - | | `discriminator` | yes | A JSONPath expression pointing to the discriminator field, for example `$.credential.type`. | | `variants` | yes | Map of discriminator value → JSON Schema. At least one variant. | | `required_variants` | no | Variant names backends must implement. Each must exist in `variants`. | ```json theme={null} "request": { "discriminator": "$.credential.type", "variants": { "pan": { "$ref": "#/components/schemas/PanRequest" }, "token": { "$ref": "#/components/schemas/TokenRequest" } }, "required_variants": ["pan"] } ``` Backends implement a discriminated action by its `action.variant` key (for example `assess.pan`). See [Backends](/products/link/concepts/backends#mapping). ## Responses `responses` is keyed by HTTP status code (a string matching `^[1-5][0-9]{2}$`), with at least one entry. Each entry nests the schema under `content` → `application/json` → `schema`: ```json theme={null} "responses": { "200": { "content": { "application/json": { "schema": { "$ref": "#/components/schemas/AssessResult" } } } } } ``` Backend response mappings target these status codes and are validated against the corresponding schema at backend-write time — see [Backends](/products/link/concepts/backends#response-mapping). ## Components `components.schemas` holds reusable schemas referenced from anywhere in the protocol via a JSON Pointer `$ref` (for example `#/components/schemas/Transaction`): ```json theme={null} "components": { "schemas": { "Transaction": { "type": "object", "properties": { "amount": { "type": "integer" }, "currency": { "type": "string" } }, "required": ["amount", "currency"] } } } ``` ## Supported JSON Schema keywords Schemas (in requests, responses, and components) are JSON Schema. The meta-schema explicitly lists the supported keywords; full validation is delegated to a JSON Schema validator (draft 2020-12): `type`, `const`, `enum`, `properties`, `required`, `additionalProperties`, `items`, `oneOf`, `anyOf`, `allOf`, `$ref`, `description`, `format`, `minimum`, `maximum`, `minLength`, `maxLength`, `pattern`. ## Complete example A minimal but valid protocol with one discriminated `assess` action and shared components: ```json theme={null} { "$schema": "https://developer.hellgate.io/schemas/link-protocol/v1", "$id": "https://acme.example/protocols/risk/v1", "name": "Acme Risk", "version": "1.0.0", "actions": { "assess": { "method": "POST", "description": "Score a transaction for fraud risk.", "request": { "discriminator": "$.credential.type", "variants": { "pan": { "type": "object", "properties": { "credential": { "$ref": "#/components/schemas/PanCredential" }, "transaction": { "$ref": "#/components/schemas/Transaction" } }, "required": ["credential", "transaction"] } }, "required_variants": ["pan"] }, "responses": { "200": { "content": { "application/json": { "schema": { "type": "object", "properties": { "type": { "const": "enum" }, "value": { "type": "string", "enum": ["ALLOW", "REVIEW", "BLOCK"] }, "backend_reference": { "type": "string" } }, "required": ["type", "value"] } } } } } } }, "components": { "schemas": { "PanCredential": { "type": "object", "properties": { "type": { "const": "pan" }, "pan": { "type": "object", "properties": { "value": { "type": "string" }, "expiry_month": { "type": "integer" }, "expiry_year": { "type": "integer" } }, "required": ["value"] } }, "required": ["type", "pan"] }, "Transaction": { "type": "object", "properties": { "amount": { "type": "integer" }, "currency": { "type": "string" } }, "required": ["amount", "currency"] } } } } ``` ## Import and validation Import a protocol you authored on a [Standalone](/products/link/plans-and-access) instance — by URL or by posting the document directly. Requires `admin:protocols:write`. ```bash theme={null} # Import by URL curl -X POST https://{instance}.eu1.on-hellgate.cloud/api/admin/protocols/import \ -H "Authorization: Bearer $TOKEN" \ -H "Content-Type: application/json" \ -d '{"url": "https://acme.example/protocols/risk/v1"}' # Or create from the document curl -X POST https://{instance}.eu1.on-hellgate.cloud/api/admin/protocols \ -H "Authorization: Bearer $TOKEN" \ -H "Content-Type: application/json" \ -d @risk-v1.json ``` At import, Link validates the document against the meta-schema and rejects anything non-conforming. A Standalone instance also lifts the restriction that limits a Bundled instance to its Specter protocols, so your own `$id` is accepted. Once imported, Link generates an OpenAPI 3.1 specification for the protocol — see [Protocols](/products/link/concepts/protocols#generated-openapi). ## Next steps Bind your protocol to a real provider with mapping rules. The protocol concept — actions, variants, and versioning. # Protocols Source: https://developer.hellgate.io/products/link/concepts/protocols A protocol is a versioned, schema-validated API contract: the actions callers invoke and the shape of each request and response. A **protocol** is the contract at the heart of Link. It declares a set of **actions** and the input and output shape of each — independent of any provider's wire format. Callers code against the protocol; [backends](/products/link/concepts/backends) bind it to real providers. A protocol is a JSON document validated against Link's **meta-schema**. Importing it stores the contract, generates an OpenAPI 3.1 specification for it, and makes its actions invokable. ## Anatomy ```json theme={null} { "$schema": "https://developer.hellgate.io/schemas/link-protocol/v1", "$id": "https://developer.hellgate.io/protocols/specter/v1", "name": "Specter v1", "version": "1.0.0", "actions": { "assess": { "method": "POST", "description": "Score a transaction for fraud risk.", "request": { "...": "..." }, "responses": { "200": { "...": "..." } } } } } ``` * **`$id`** — the canonical URL identifying this protocol. Backends reference the protocol by this URL, and it is the join key between a protocol and the backends that implement it. * **`name`** / **`version`** — human-readable identity and a version string. * **`actions`** — the operations the protocol exposes (see below). * **`components`** — optional reusable schema definitions referenced across actions. ## Actions An **action** is a named operation — for example `assess`. Each action declares: * A **method** — one of `POST`, `GET`, `PUT`, `PATCH`, `DELETE`. Callers must invoke the action with this method. * A **description** — a human-readable summary. * A **request** schema — the input the caller sends. * A set of **responses** — keyed by HTTP status code, each with an output schema. Action names may use **dot notation** to namespace related operations — for example `notify.authorization`, `notify.chargeback`, `notify.fraud_report`. For the exact field-by-field definition, see the [Protocol schema](/products/link/concepts/protocol-schema) reference. ## Variants and discriminators An action's request can be either: * **Monomorphic** — a single request schema, or * **Polymorphic** — a discriminated union. A **discriminator** field in the request selects which **variant** applies. For example, the `assess` action might discriminate on `credential.type`: a request with `credential.type: "pan"` resolves to the `assess.pan` variant, while `credential.type: "token"` resolves to `assess.token`. Backends implement variants by their full key (`assess.pan`), and Link rejects a backend that misses a required action or variant. ## Versioning The protocol `version` and its `$id` together identify a contract. A new version is a new protocol document with its own `$id` — existing backends keep targeting the version they were bound to, so contract changes never silently alter live behavior. ## Meta-schema Every protocol is validated against the Link protocol meta-schema (`link-protocol-v1`) at import time. A protocol that does not conform is rejected. The meta-schema is served by each instance: ```bash theme={null} curl https://developer.hellgate.io/schemas/link-protocol/v1 ``` Standalone On a [Bundled](/products/link/plans-and-access) instance, imports are restricted to the protocols that serve its Specter instance. To author and import **your own** protocols, you need a [Standalone](/products/link/plans-and-access) instance; see the [Protocol schema](/products/link/concepts/protocol-schema) reference. ## Generated OpenAPI Because a protocol fully describes its actions and schemas, Link generates an **OpenAPI 3.1** specification for each imported protocol. This is the contract a *consumer* of the protocol integrates against — the invoke surface for that specific protocol — and is retrieved per protocol: ```bash theme={null} curl -H "Authorization: Bearer $TOKEN" \ https://{instance}.eu1.on-hellgate.cloud/api/admin/protocols/specter-v1/openapi ``` Link operates at the protocol level: the invoke API is *defined by whichever protocol you import*, not by a single fixed Link API. To document the endpoints your callers use, generate the OpenAPI for that protocol. ## Next steps Bind a protocol to a real provider with credentials, authentication, and mapping rules. Call a protocol action and handle the result. # Overview Source: https://developer.hellgate.io/products/link/overview Link is a protocol-abstraction runtime: define an API contract once, bind it to any provider, and switch what's behind it without changing the caller. Link is the **protocol-abstraction runtime** of the [Composable Payment Architecture](/platform/cpa). It sits between an API and whatever fulfils it: you define a versioned API contract — a **protocol** — and bind it to one or more real providers through declarative **backends**. Callers invoke the protocol; Link maps each request onto the configured backend, dispatches it, maps the response back, and logs the exchange. The result is a clean split between the API a consumer codes against and the system that actually serves it. **Change what's behind the API without changing the API.** ## Role in the Hellgate Cloud Platform Like every service on the platform, Link can be used standalone or in composition with others. Within the platform it is the **PSP and provider connectivity layer** — the component that [Specter](/products/specter/overview) reaches through to call external risk engines, and that [Commerce](/products/commerce/v2/overview) routes through to reach acquirers and payment service providers. Where other services own a capability (Guardian owns tokenization, Specter owns decisioning), Link owns the *connection*: one runtime that decouples consumers from vendor wire formats so that integration becomes **configuration, not code**. ## Core concepts A versioned, schema-validated API contract — the **actions** callers invoke (`assess`, `authorize`, `capture`, `refund`) and the shape of each request and response. A binding from a protocol to a real provider — credentials, an authentication strategy, and the declarative **mapping rules** that translate the contract into that provider's wire format. Calling a protocol action. Link selects a matching backend, runs the request mapping, dispatches the call, maps the response back, and writes an execution log. ## How it works A request arrives against a protocol action. Link validates it against the action's schema, maps it onto the chosen backend with declarative rules, dispatches the HTTP call, maps the response back to the protocol's shape, and records an execution entry. ```mermaid theme={null} sequenceDiagram participant C as Caller participant L as Link participant P as Provider C->>L: POST /api/invoke/{protocol}/{action} Note over L: Validate · select backend · map request L->>P: Provider-native request P-->>L: Provider-native response Note over L: Map response · write execution log L-->>C: Protocol-shaped result ``` Because the contract and the provider are separate, you can point the same protocol at a different backend — or run several side by side — without the caller ever knowing. ## What you get by default Every protocol inherits a shared operational surface, so each new integration starts with it rather than rebuilding it: * **Declarative mapping** — adapter logic lives in versioned templates and a filter chain, not in per-vendor code or deploys. * **Generated OpenAPI** — each imported protocol produces its own OpenAPI 3.1 specification. * **Message-level encryption** — optional JWE encryption of request and response bodies, with rotatable keys, for providers that require it. * **Credential and key rotation** — rotate backend credentials and encryption keys without downtime. * **Execution audit log** — every invocation is recorded asynchronously with timing and the provider response. * **Outbound safety** — backend hosts and imported protocol URLs are validated against an SSRF guard (HTTPS-only, no private or internal addresses) when you register or update them. ## Integration as leverage The split between contract and provider is not just an architectural convenience — it is the point. * **Front of house** — present the API a customer already uses. They re-point rather than re-build, so a migration becomes *your* configuration instead of *their* project. * **Back of house** — bind that same API to any provider, and switch or combine providers, without the customer ever knowing. This is how Hellgate runs its own platform: real volume flows through Link today, emulating the functionality that matters across very different backends behind single, stable contracts. For the commercial framing of this model, see the [Composable Payment Architecture](/platform/cpa) overview. ## Next steps The Bundled and Standalone editions, tiers, and how to obtain a Link instance. Import a protocol, register a backend, and invoke your first action end to end. # Plans & Access Source: https://developer.hellgate.io/products/link/plans-and-access Link editions — Bundled and Standalone — environment tiers, and how to obtain an instance. Link is delivered as **single-tenant managed infrastructure** — each instance is dedicated to one organization and serves one contract, with no multi-tenancy inside an instance. It comes in two **editions** that differ in what the instance is allowed to serve. ## Editions Provisioned together with a [Specter](/products/specter/overview) instance and locked to serving that instance's backends. It scales automatically with the connected Specter and is billed as part of it — there is no separate Link contract. The full open runtime. Run any Hellgate-curated protocol, import **your own** protocols that conform to the [Link protocol schema](/products/link/concepts/protocol-schema), and put other load through the instance — independent of Specter. | Edition | Serves | Best for | | - | - | - | | **Bundled** | The connected Specter instance's backends only | Specter customers who only need backends for decisioning | | **Standalone** | Any Hellgate-curated protocol, your own protocols, other load | Teams using Link beyond Specter, or defining their own protocols | **Unlocking.** A Bundled instance is dedicated to its Specter instance. To run other protocols or send other load through it, unlock it into a **Standalone** instance — contact your account representative. **Managed backends.** You operate your own backends on either edition. If you'd rather not, Hellgate can operate backends on your behalf — ask your account representative. ## Tiers Each edition runs on two environment tiers, each a separate instance: * **Production** — your live environment for real provider traffic. * **Test** — an environment for development, integration testing, and quality assurance, including mock backends that need no external provider. Each tier is a distinct instance with its own configuration. Protocols and backends registered in Test are not shared with Production. ## Getting access To request a Link instance, contact your account representative or reach out via [hellgate.io/demo](https://hellgate.io/demo). For plan and pricing details, see [hellgate.io/cpa/link](https://hellgate.io/cpa/link#pricing) or contact your account representative. Once your instance is provisioned, Hellgate Support provides: * Your instance URL — `https://{instance}.{env}.on-hellgate.cloud`, where `{env}` is currently `eu1` and `{instance}` is your unique slug * Client credentials (client ID and secret) to request access tokens ## Next steps Authenticate to your instance, and the scopes that gate each endpoint. Author your own protocols on a Standalone instance. # Quickstart Source: https://developer.hellgate.io/products/link/quickstart Import a protocol, register a backend, and invoke your first action end to end. This guide takes you from a token to a live invocation: import a **protocol**, register a **backend** that binds it to a real provider, invoke an **action**, and read the **execution log**. The examples use the `specter-v1` protocol — the contract [Specter](/products/specter/overview) uses to reach external risk engines — but the flow is identical for any protocol. ## Prerequisites * A Link instance and its base URL (see [Plans & Access](/products/link/plans-and-access)). * A bearer token. The steps below note the scope each call needs — see [Authentication](/products/link/authentication). * Credentials for the provider you want to connect (this example uses a card-risk provider). Your instance URL follows the pattern `https://{instance}.eu1.on-hellgate.cloud` — `{instance}` is your unique slug and `eu1` is the current environment. Substitute it in the examples below, and set `$TOKEN` to a bearer token that holds the scope named in each step. ## Steps A protocol is a versioned contract, imported by URL. The URL must return a document matching the [protocol meta-schema](/products/link/concepts/protocols#meta-schema). Requires `admin:protocols:write`. ```bash theme={null} curl -X POST https://{instance}.eu1.on-hellgate.cloud/api/admin/protocols/import \ -H "Authorization: Bearer $TOKEN" \ -H "Content-Type: application/json" \ -d '{"url": "https://developer.hellgate.io/protocols/specter/v1"}' ``` The response includes the `local_id` you will use when invoking — here, `specter-v1`. Inspect the action requirements (useful when writing mapping rules): ```bash theme={null} curl -H "Authorization: Bearer $TOKEN" \ https://{instance}.eu1.on-hellgate.cloud/api/admin/protocols/specter-v1/requirements/assess ``` A backend binds the protocol to a real provider — its host, credentials, an authentication strategy, and the [mapping rules](/products/link/concepts/backends#mapping) that translate the protocol request into the provider's wire format and the response back. Requires `admin:backends:write`. ```bash theme={null} curl -X POST https://{instance}.eu1.on-hellgate.cloud/api/admin/backends \ -H "Authorization: Bearer $TOKEN" \ -H "Content-Type: application/json" \ -d '{ "id": "acme-risk", "name": "Acme Risk", "protocol": "https://developer.hellgate.io/protocols/specter/v1", "host": "api.acme.example", "credentials": { "token": "secret_key_test_..." }, "auth_pipeline": { "source_type": "inline", "credential_type": "bearer" }, "connections": { "assess.pan": { "endpoint": { "method": "POST", "path": "/v1/decisions", "content_type": "application/json", "timeout_ms": 5000 }, "request_mapping": { "body": { "cardNumber": "{{ $.request.body.credential.pan.value | required }}", "amount": "{{ $.request.body.transaction.amount | required }}", "currency": "{{ $.request.body.transaction.currency | required }}" } }, "response_mapping": { "200": { "return": "200", "body": { "type": "enum", "value": "{{ $.response.body.status | map({ ACCEPTED: ALLOW, REJECTED: BLOCK, PENDING: REVIEW }) | required }}", "backend_reference": "{{ $.response.body.id | omit_if_null }}" } } } } } }' ``` Mapping leaves are strings carrying the mustache-style template grammar — `{{ expr | filter }}`. Paths are scoped: `$.request.body.*` and `$.request.header.*` for the caller's request, `$.response.body.*` and `$.response.header.*` for the provider's response. See [Backends](/products/link/concepts/backends) for the full mapping model. Call the protocol action. The HTTP method must match the action's declared method. Requires `invoke:execute`. ```bash theme={null} curl -X POST https://{instance}.eu1.on-hellgate.cloud/api/invoke/specter-v1/assess \ -H "Authorization: Bearer $TOKEN" \ -H "Content-Type: application/json" \ -d '{ "credential": { "type": "pan", "pan": { "value": "4111111111111111", "expiry_month": 12, "expiry_year": 2030 } }, "transaction": { "amount": 4999, "currency": "USD" } }' ``` Link validates the request, selects the matching backend, runs the mapping, and returns the protocol-shaped result: ```json theme={null} { "type": "enum", "value": "ALLOW", "backend_reference": "dec-xyz" } ``` The response carries an `x-link-execution` header with the execution ID, and `server-timing` with the call timing. If more than one enabled backend implements the action, Link returns `409` — disambiguate with `?backend=acme-risk`. See [Invocation](/products/link/concepts/invocation) for method matching, backend selection, and error codes. Every invocation is logged asynchronously. Fetch it by ID. Requires `admin:executions:read`. ```bash theme={null} curl -H "Authorization: Bearer $TOKEN" \ https://{instance}.eu1.on-hellgate.cloud/api/admin/executions/ ``` The entry contains the protocol-mapped result, the raw provider response, timing, the backend that handled the call, and any error detail. ## Next steps Actions, variants, versioning, and the meta-schema. The mapping engine, authentication strategies, and mocks. Method matching, backend selection, and error handling.