The Secure Proxy feature allows payment providers that are not PCI-certified to securely process card payments by routing sensitive card data through VTEX’s PCI-certified infrastructure.
Overview
When usesSecureProxy is enabled, your connector receives encrypted card tokens instead of raw card data, and a secureProxyUrl that routes requests through VTEX’s secure infrastructure to PCI-certified endpoints.
Apps implemented within VTEX IO cannot have the Secure Proxy disabled. This option only works for configuration apps. All IO connectors must use Secure Proxy unless they are PCI-certified entities.
Configuration
Enable Secure Proxy in your paymentProvider/configuration.json:
{
"name" : "MyConnector" ,
"usesSecureProxy" : true ,
"paymentMethods" : [
{
"name" : "Visa" ,
"allowsSplit" : "onCapture"
}
]
}
The usesSecureProxy option defaults to true for VTEX IO payment providers.
Implementation
To use Secure Proxy, you need to:
Extend the SecureExternalClient class
Configure your PCI-certified endpoint
Use the secureProxyUrl received in payment requests
Create a Secure External Client
Extend SecureExternalClient with your PCI-certified endpoint: node/clients/SecureClient.ts
import { SecureExternalClient } from '@vtex/payment-provider'
import type {
InstanceOptions ,
IOContext ,
RequestConfig ,
} from '@vtex/api'
import { CardAuthorization } from '@vtex/payment-provider'
export class MyPCICertifiedClient extends SecureExternalClient {
constructor ( protected context : IOContext , options ?: InstanceOptions ) {
super ( 'http://my-pci-certified-domain.com' , context , options )
}
// Implement your payment methods
}
The endpoint http://my-pci-certified-domain.com must be approved by VTEX Secure Proxy by submitting the Attestation of Compliance (AOC) that includes this endpoint.
Implement secure payment methods
Use the secureProxyUrl from the authorization request: node/clients/SecureClient.ts
export class MyPCICertifiedClient extends SecureExternalClient {
constructor ( protected context : IOContext , options ?: InstanceOptions ) {
super ( 'http://my-pci-certified-domain.com' , context , options )
}
public processPayment = ( cardRequest : CardAuthorization ) => {
return this . http . post (
'api/v1/payments' ,
{
holder: cardRequest . holderToken ,
number: cardRequest . numberToken ,
expiration: cardRequest . expiration ,
csc: cardRequest . cscToken ,
},
{
headers: {
Authorization: 'Bearer your-api-key' ,
'Content-Type' : 'application/json' ,
},
secureProxy: cardRequest . secureProxyUrl ,
} as RequestConfig
)
}
}
Use the client in your connector
Access the secure client from your connector: import {
PaymentProvider ,
AuthorizationRequest ,
AuthorizationResponse ,
Authorizations ,
isCardAuthorization ,
} from '@vtex/payment-provider'
export default class MyConnector extends PaymentProvider {
public async authorize (
request : AuthorizationRequest
) : Promise < AuthorizationResponse > {
if ( isCardAuthorization ( request )) {
const { card } = request
// Use the secure client
const response = await this . context . clients . myPCIClient . processPayment ( card )
return Authorizations . approve ( request , {
authorizationId: response . transactionId ,
nsu: response . nsu ,
tid: response . tid ,
})
}
throw new Error ( 'Unsupported payment method' )
}
}
Card data tokenization
When Secure Proxy is enabled, card data is tokenized:
Original Field Tokenized Field Description card.numbercard.numberTokenEncrypted card number card.holdercard.holderTokenEncrypted cardholder name card.csccard.cscTokenEncrypted security code card.expirationcard.expirationExpiration date (not encrypted)
Supported content types
VTEX Secure Proxy currently supports two content types:
application/json
application/x-www-form-urlencoded
Any other Content-Type will not be supported by the Secure Proxy and will result in errors.
public processFormEncodedPayment = ( cardRequest : CardAuthorization ) => {
const formData = new URLSearchParams ()
formData . append ( 'card_holder' , cardRequest . holderToken )
formData . append ( 'card_number' , cardRequest . numberToken )
formData . append ( 'card_exp' , cardRequest . expiration . month + cardRequest . expiration . year )
formData . append ( 'card_cvv' , cardRequest . cscToken )
return this . http . post (
'api/v1/charge' ,
formData . toString (),
{
headers: {
'Content-Type' : 'application/x-www-form-urlencoded' ,
Authorization: 'Bearer your-api-key' ,
},
secureProxy: cardRequest . secureProxyUrl ,
} as RequestConfig
)
}
AOC submission process
To use Secure Proxy with your PCI-certified endpoint:
Obtain PCI-DSS AOC
Ensure your payment processor has a valid PCI-DSS Attestation of Compliance that includes the endpoint you want to use.
Submit AOC to VTEX
Contact VTEX support or your implementation team to submit the AOC document with the endpoint URL.
Wait for approval
VTEX will review and approve the endpoint for use with Secure Proxy.
Configure your connector
Once approved, configure your SecureExternalClient with the approved endpoint URL.
Testing secure proxy
During development, you can test your Secure Proxy implementation:
import { isTokenizedCard } from '@vtex/payment-provider'
public async authorize (
request : AuthorizationRequest
): Promise < AuthorizationResponse > {
if ( isCardAuthorization ( request )) {
const { card } = request
// Check if card is tokenized (Secure Proxy enabled)
if ( isTokenizedCard ( card )) {
console . log ( 'Using Secure Proxy with tokens' )
// Process with tokens
} else {
console . log ( 'Processing without Secure Proxy' )
// Direct processing (only for PCI-certified connectors)
}
}
}
Best practices
Always validate secureProxyUrl
Check that secureProxyUrl is present before making requests: if ( ! cardRequest . secureProxyUrl ) {
throw new Error ( 'Secure Proxy URL not provided' )
}
Use the provided type guards to check card type: import { isTokenizedCard , isCardAuthorization } from '@vtex/payment-provider'
if ( isCardAuthorization ( request ) && isTokenizedCard ( request . card )) {
// Safe to access tokenized fields
}
Implement proper error handling for Secure Proxy requests: try {
const response = await this . context . clients . secureClient . processPayment ( card )
return Authorizations . approve ( request , response )
} catch ( error ) {
return Authorizations . deny ( request , {
message: 'Payment processing failed' ,
})
}
Even with tokenized data, avoid logging card information: // ❌ Don't do this
console . log ( 'Card data:' , card )
// ✅ Do this instead
console . log ( 'Processing payment for order:' , request . orderId )