Salesforce Connector
Quantum-safe credential vaulting, field-level encryption, bulk encryption, and outbound proxy for Salesforce - delivered as a Managed Package 2GP (namespace: quantaseal).
quantasealType:Managed Package 2GPAlgorithm:ML-KEM-768 + ML-DSA-65FIPS:203 / 204 / 205Overview#
The QuantaSeal Salesforce Connector integrates directly into your Salesforce org as a certified Managed Package. Once installed, it allows any Apex class, Flow, or LWC component to encrypt and sign data using NIST FIPS 203/204/205 post-quantum cryptography - without managing keys, certificates, or cryptographic libraries inside Salesforce.
All cryptographic operations are performed server-side by the QuantaSeal API at https://api.quantaseal.io. Salesforce acts as a consumer: it sends plaintext (or ciphertext) over TLS to QuantaSeal and receives the result. Your API key and tenant credentials never leave the QuantaSeal vault - they are stored in Named Credentials and never exposed in Apex code.
Use cases
- ✓Encrypt sensitive Salesforce field values (SSN, TFN, credit card, health record) at rest using ML-KEM-768 + AES-256-GCM before they touch the database.
- ✓Store OAuth2 tokens, API keys, and integration secrets in the QuantaSeal Vault rather than Salesforce Custom Settings or Named Credentials plaintext.
- ✓Run bulk encryption jobs over large record sets (Contacts, Cases, custom objects) on a schedule - without hitting Apex governor limits.
- ✓Route outbound HTTP callouts (to SAP, NetSuite, REST APIs) through the QuantaSeal proxy engine so all traffic is encrypted and auditable.
- ✓Receive inbound webhooks from external systems with quantum-safe signature verification before data enters Salesforce.
- ✓Generate SOC 2, HIPAA, PCI DSS, or ISO 27001 compliance evidence for Salesforce data operations from the QuantaSeal audit hash chain.
Apex classes included
QuantaSealAuthProviderOAuth2 Named Credentials provider implementing Auth.AuthProvider. Handles the JWT Bearer flow (RFC 7521) so Salesforce can authenticate to QuantaSeal using a Connected App certificate instead of static API keys.QuantaSealProxyServiceOutbound proxy service. Unseals credentials from the QuantaSeal vault on demand and routes HTTP callouts through the QuantaSeal proxy engine with full PQC encryption and audit logging.QuantaSealFieldEncryptionControllerLWC Apex controller for field-level encrypt and decrypt operations in the Salesforce UI. Used by the QuantaSeal Field Encryption LWC component included in the package.QuantaSealBulkEncryptBatchSchedulable Batchable Apex class. Processes records in 200-record batches (respecting governor limits) to encrypt or decrypt a specified field across large data volumes.QuantaSealPostInstallPost-install script that runs automatically on package install and upgrade. Creates the QuantaSeal_Settings__mdt Custom Metadata record with your Tenant ID, API Endpoint, and Client ID, and initialises outbound webhook registration.Prerequisites#
Salesforce org requirements
| Requirement | Minimum | Notes |
|---|---|---|
| Edition | Enterprise or Unlimited | Developer Edition works for testing. Professional Edition is not supported - no Apex. |
| API Access | Enabled | Required for all callouts to api.quantaseal.io. Confirm under Setup > Company Settings > API. |
| Apex | Enabled | Must allow Apex execution. Available in Enterprise+. |
| Remote Site Settings | Configured | api.quantaseal.io must be allowlisted (the post-install script does this automatically). |
| Named Credentials | Available | Used to securely store and retrieve the QuantaSeal API endpoint and authentication context. |
| LWC | Enabled | Required for the Field Encryption component. Available in all Lightning-enabled orgs. |
QuantaSeal account requirements
- 1QuantaSeal account on the Professional plan or higher - Starter plan does not include the Salesforce connector.
- 2Tenant ID - Available at app.quantaseal.io > Settings > Tenant.
- 3API key (qs_live_...) - Generated at app.quantaseal.io > Security > API Keys.
- 4Connected App certificate (for JWT Bearer flow) - A self-signed X.509 certificate PEM - generated locally and uploaded to QuantaSeal. The private key stays in Salesforce. See Authentication section.
Installation#
The QuantaSeal Salesforce Connector is installed as a Managed Package 2GP. Installation takes approximately five minutes. The post-install script handles all configuration automatically - you only need to supply your Tenant ID and API key.
Install from AppExchange#
Navigate to the QuantaSeal listing on Salesforce AppExchange.
Search for QuantaSeal or use the direct install URL. The package namespace is quantaseal.
Click Get It Now and select your production or sandbox org.
Choose Install for Admins Only on the first install. You can grant access to additional users via the QuantaSeal_User Permission Set after installation.
Approve the required permissions and click Install.
The package requires access to: make callouts to api.quantaseal.io, create Custom Metadata records, and read/write Named Credential configurations.
Wait for the install email from Salesforce (usually 2–3 minutes).
Large orgs may take up to 10 minutes. The QuantaSealPostInstall script runs automatically once the package is installed.
Post-Install Configuration#
After installation completes, Salesforce opens the QuantaSeal Setup Assistant. This screen is served by the QuantaSealPostInstall Apex class. Fill in the three fields and click Save & Verify.
QuantaSeal Setup Assistant - required fields
Tenant ID
Your UUID from app.quantaseal.io > Settings > Tenant
550e8400-e29b-41d4-a716-446655440000API Key
Your live API key - starts with qs_live_
qs_live_abc123...API Endpoint
Always this exact URL - do not modify
https://api.quantaseal.ioOn save, QuantaSealPostInstall performs the following automatically:
- Creates a QuantaSeal_Settings__mdt Custom Metadata record with your Tenant ID, API Endpoint, Client ID, and webhook signing key.
- Registers api.quantaseal.io in Remote Site Settings so Apex callouts are allowed.
- Creates the QuantaSeal_API Named Credential with the saved endpoint URL.
- Calls POST /api/v2/integrations to register this Salesforce org as an integration in your QuantaSeal tenant.
- Performs a health check against GET /health and shows the result on screen.
Assign Permission Sets#
The package includes one Permission Set: QuantaSeal_User. Assign it to every Salesforce user who needs to run encrypt/decrypt operations or call the proxy service.
1. Go to Setup > Users > Permission Sets
2. Click QuantaSeal_User
3. Click Manage Assignments > Add Assignments
4. Select the users who need access, then click AssignSystem Administrators already have access to all Apex classes. The Permission Set is required for non-admin users running LWC-based encryption in Salesforce Lightning pages.
Verify Connection#
Confirm that the Named Credential is correctly configured and that Salesforce can reach the QuantaSeal API.
1. Go to Setup > Security > Named Credentials
2. Find QuantaSeal_API in the list
3. Confirm:
URL: https://api.quantaseal.io
Authentication: Named Principal
Protocol: OAuth 2.0 (JWT Bearer)
Auth. Provider: QuantaSeal
4. Click Authenticate to run the JWT Bearer exchange
→ Browser should redirect back with "Success"You can also run a quick connectivity check from the Developer Console:
// Execute in Developer Console > Execute Anonymous
HttpRequest req = new HttpRequest();
req.setEndpoint('callout:QuantaSeal_API/api/v2/health');
req.setMethod('GET');
Http h = new Http();
HttpResponse res = h.send(req);
System.debug('QuantaSeal health: ' + res.getBody());
// Expected: {"status":"healthy","version":"2.x.x"}401 or a callout exception, verify that your API key in QuantaSeal_Settings__mdt matches a live key in the QuantaSeal Admin Console and that the Named Credential authentication provider is correctly linked. See the Troubleshooting section for detailed error codes.Configure Named Credentials#
Salesforce Named Credentials store the endpoint URL and authentication context so Apex callouts never contain hardcoded secrets. The post-install script creates theQuantaSeal_API Named Credential automatically, but you must link it to a Connected App for the JWT Bearer flow to work.
Step 1 - Create a Connected App
1. Go to Setup > Apps > App Manager > New Connected App
2. Fill in:
Connected App Name: QuantaSeal Integration
API Name: QuantaSeal_Integration
Contact Email: your-admin@example.com
3. Check "Enable OAuth Settings"
4. Callback URL: https://login.salesforce.com/services/oauth2/callback
5. Selected OAuth Scopes:
- Full access (full)
- Perform requests at any time (refresh_token, offline_access)
6. Check "Use digital signatures"
7. Upload your certificate file (quantaseal.crt - see Authentication section)
8. Save - note the Consumer Key (client_id)Step 2 - Create an Auth. Provider
1. Go to Setup > Security > Auth. Providers > New
2. Provider Type: Apex Class
3. Apex Class: QuantaSealAuthProvider
4. Name: QuantaSeal
5. URL Suffix: QuantaSeal
6. Custom Parameters (key=value pairs):
tenant_id = <your QuantaSeal Tenant UUID>
client_id = <Connected App Consumer Key>
endpoint = https://api.quantaseal.io
7. SaveStep 3 - Link Named Credential to Auth. Provider
1. Go to Setup > Security > Named Credentials
2. Click QuantaSeal_API > Edit
3. Authentication Protocol: OAuth 2.0
4. Authentication Provider: QuantaSeal (the Auth. Provider created above)
5. Scope: full refresh_token
6. Check "Start Authentication Flow on Save"
7. Save - complete the OAuth flow in the popup windowQuantaSeal_API. All Apex classes in the package reference this label using callout:QuantaSeal_API. Renaming it will break all callouts until you update the Custom Metadata record.Authentication#
The QuantaSeal Salesforce Connector authenticates using the OAuth 2.0 JWT Bearer Token Flow defined in RFC 7521. This eliminates the need to store static API keys in Apex code or Custom Settings. Instead, Salesforce signs a short-lived JWT with the Connected App private key and exchanges it for a QuantaSeal access token.
How the JWT Bearer flow works
Generate the certificate
Generate a self-signed certificate on your local machine. Upload the .crt to your Salesforce Connected App and the .pemto the QuantaSeal Admin Console under Settings > Salesforce Connected App.
# Generate a 2048-bit RSA key pair (private key stays local - never uploaded to QuantaSeal)
openssl req -x509 -newkey rsa:2048 -keyout quantaseal.key -out quantaseal.crt \
-days 365 -nodes -subj "/CN=QuantaSeal Salesforce Connector"
# The .crt file → upload to Salesforce Connected App > Use Digital Signatures
# The .crt file → also upload to QuantaSeal Admin Console > Settings > Salesforce Connected App
# The .key file → stays on your local machine, loaded into Salesforce Certificate and Key Management
# Verify the certificate
openssl x509 -in quantaseal.crt -text -noout | grep -E "(Subject|Not After|Public Key)"QuantaSealAuthProvider - how it works
The QuantaSealAuthProvider Apex class implements Auth.AuthProvider. Salesforce calls it automatically when a Named Credential needs an access token. You do not call it directly.
// QuantaSealAuthProvider.cls (excerpt - full source in package)
global class QuantaSealAuthProvider implements Auth.AuthProvider {
// Called by Salesforce to exchange the JWT for an access token
global Auth.AuthProviderTokenResponse handleCallback(
Map<String,String> config,
Auth.AuthProviderCallbackState state
) {
// 1. Build JWT claims
String tenantId = config.get('tenant_id');
String clientId = config.get('client_id');
String endpoint = config.get('endpoint');
Long expiry = Datetime.now().addSeconds(180).getTime() / 1000;
Map<String,Object> claims = new Map<String,Object>{
'iss' => clientId,
'sub' => UserInfo.getUserName(),
'aud' => endpoint,
'exp' => expiry,
'iat' => Datetime.now().getTime() / 1000,
'tenant_id' => tenantId
};
// 2. Sign JWT with the Connected App private key (via Salesforce Auth.JWT)
Auth.JWT jwt = new Auth.JWT();
jwt.setAdditionalClaims(claims);
Auth.JWS jws = new Auth.JWS(jwt, 'QuantaSeal_Certificate');
String signedJwt = jws.getCompactSerialization();
// 3. Exchange JWT for QuantaSeal access token
HttpRequest req = new HttpRequest();
req.setEndpoint(endpoint + '/api/v2/auth/oauth2-token');
req.setMethod('POST');
req.setHeader('Content-Type', 'application/x-www-form-urlencoded');
req.setBody(
'grant_type=' + EncodingUtil.urlEncode(
'urn:ietf:params:oauth:grant-type:jwt-bearer', 'UTF-8'
) + '&assertion=' + EncodingUtil.urlEncode(signedJwt, 'UTF-8')
);
Http h = new Http();
HttpResponse res = h.send(req);
Map<String,Object> body = (Map<String,Object>)
JSON.deserializeUntyped(res.getBody());
return new Auth.AuthProviderTokenResponse(
'QuantaSeal',
null, // no OAuth state here
(String) body.get('access_token'),
(String) body.get('refresh_token')
);
}
}qs_live_...) directly in Apex code, Custom Settings, or Custom Labels. Always use the JWT Bearer flow via Named Credentials. Hardcoded API keys in Apex source are visible to all developers with Apex class read access and will appear in code review history.Field Encryption#
The QuantaSealFieldEncryptionController Apex class is the backend for the QuantaSeal Field Encryption LWC component. It encrypts or decrypts a single field value using the QuantaSeal API and returns the result to the component. You can also call it directly from any Apex trigger, flow action, or process builder.
Apex controller - encrypt a field value
// Encrypt a single field value - typically called from a trigger or flow
@AuraEnabled
public static String encryptFieldValue(String plaintext) {
// Build request to QuantaSeal Encryption API
HttpRequest req = new HttpRequest();
req.setEndpoint('callout:QuantaSeal_API/api/v2/encryption/encrypt');
req.setMethod('POST');
req.setHeader('Content-Type', 'application/json');
Map<String,Object> payload = new Map<String,Object>{
'data' => plaintext,
'algorithm' => 'ML-KEM-768'
};
req.setBody(JSON.serialize(payload));
Http h = new Http();
HttpResponse res = h.send(req);
if (res.getStatusCode() != 200) {
throw new CalloutException(
'QuantaSeal encryption failed: ' + res.getStatusCode()
);
}
Map<String,Object> body = (Map<String,Object>)
JSON.deserializeUntyped(res.getBody());
Map<String,Object> data = (Map<String,Object>) body.get('data');
// Store the entire envelope as a JSON string in your Text (Long) field
return JSON.serialize(data);
}
// Decrypt a previously encrypted field value
@AuraEnabled
public static String decryptFieldValue(String encryptedEnvelope) {
Map<String,Object> envelope = (Map<String,Object>)
JSON.deserializeUntyped(encryptedEnvelope);
HttpRequest req = new HttpRequest();
req.setEndpoint('callout:QuantaSeal_API/api/v2/encryption/decrypt');
req.setMethod('POST');
req.setHeader('Content-Type', 'application/json');
req.setBody(JSON.serialize(envelope));
Http h = new Http();
HttpResponse res = h.send(req);
if (res.getStatusCode() != 200) {
throw new CalloutException(
'QuantaSeal decryption failed: ' + res.getStatusCode()
);
}
Map<String,Object> body = (Map<String,Object>)
JSON.deserializeUntyped(res.getBody());
Map<String,Object> data = (Map<String,Object>) body.get('data');
return (String) data.get('data');
}Using the Field Encryption LWC component
Add the quantaseal__fieldEncryption LWC component to any Lightning Record Page via the App Builder. It renders an encrypt/decrypt button pair next to any Long Text Area field you configure.
<!-- In your custom LWC component -->
<template>
<!-- Use the packaged FieldEncryption component -->
<c-quanta-seal-field-encryption
record-id={recordId}
object-api-name="Contact"
field-api-name="TaxFileNumber__c"
label="Tax File Number"
onencrypted={handleEncrypted}
ondecrypted={handleDecrypted}>
</c-quanta-seal-field-encryption>
</template>// In your LWC controller
import { LightningElement, api } from 'lwc';
export default class MyContactForm extends LightningElement {
@api recordId;
handleEncrypted(event) {
// event.detail.ciphertext contains the encrypted envelope JSON string
// Save it to the field using uiRecordApi or a custom Apex action
const ciphertext = event.detail.ciphertext;
console.log('Encrypted:', ciphertext.substring(0, 40) + '...');
}
handleDecrypted(event) {
// event.detail.plaintext contains the decrypted value
// Display it in a masked input - do not persist plaintext back to Salesforce
const plaintext = event.detail.plaintext;
this.template.querySelector('.secret-field').value = plaintext;
}
}Trigger-based automatic encryption
Encrypt sensitive fields automatically in an Apex trigger before each insert or update:
// ContactEncryptTrigger.trigger
trigger ContactEncryptTrigger on Contact (before insert, before update) {
for (Contact c : Trigger.new) {
// Only encrypt if the field changed (avoid double-encryption)
if (c.TaxFileNumber__c != null && !c.TaxFileNumber__c.startsWith('{"ciphertext_kem"')) {
// Encrypt the TFN before it is written to the database
c.TaxFileNumber__c =
QuantaSealFieldEncryptionController.encryptFieldValue(c.TaxFileNumber__c);
}
}
}
// To decrypt on read, use a trigger on after query (API 55.0+) or a helper Apex action:
trigger ContactDecryptTrigger on Contact (after query) {
for (Contact c : Trigger.new) {
if (c.TaxFileNumber__c != null && c.TaxFileNumber__c.startsWith('{"ciphertext_kem"')) {
// Decrypt only for users with the QuantaSeal_User permission set
if (hasQuantaSealPermission()) {
c.TaxFileNumber__c =
QuantaSealFieldEncryptionController.decryptFieldValue(c.TaxFileNumber__c);
} else {
c.TaxFileNumber__c = '••••••••'; // masked for unauthorised users
}
}
}
}
private static Boolean hasQuantaSealPermission() {
return [
SELECT Id FROM PermissionSetAssignment
WHERE AssigneeId = :UserInfo.getUserId()
AND PermissionSet.Name = 'QuantaSeal_User'
LIMIT 1
].size() > 0;
}QuantaSealBulkEncryptBatch instead - see the Bulk Encryption section.Bulk Encryption#
QuantaSealBulkEncryptBatch is a Database.Batchable and Database.AllowsCallouts Apex class. It processes records in 200-record batches, making one QuantaSeal API callout per record within each batch execution, and respects all Apex governor limits.
Schedule a bulk encryption job
// Execute in Developer Console > Execute Anonymous
// Encrypt all Contacts where TaxFileNumber__c is not yet encrypted
Id jobId = Database.executeBatch(
new QuantaSealBulkEncryptBatch(
'Contact', // SObject API name
'TaxFileNumber__c', // Field API name
'encrypt', // Operation: 'encrypt' or 'decrypt'
'WHERE TaxFileNumber__c != null AND TaxFileNumber__c NOT LIKE '{"ciphertext_kem"%''
),
200 // batch size - max 200 for callout batches
);
System.debug('Bulk encrypt job started: ' + jobId);Schedule recurring encryption
Use the Apex Scheduler to run the batch on a schedule - for example, every night at midnight to catch any newly created records that the trigger missed:
// QuantaSealBulkEncryptScheduler.cls
global class QuantaSealBulkEncryptScheduler implements Schedulable {
global void execute(SchedulableContext ctx) {
Database.executeBatch(
new QuantaSealBulkEncryptBatch(
'Contact',
'TaxFileNumber__c',
'encrypt',
'WHERE TaxFileNumber__c != null AND TaxFileNumber__c NOT LIKE '{"ciphertext_kem"%''
),
200
);
}
}
// Schedule to run every night at midnight (CRON: Seconds Minutes Hours Day Month Week Year)
String cronExp = '0 0 0 * * ?';
System.schedule('QuantaSeal Nightly TFN Encrypt', cronExp, new QuantaSealBulkEncryptScheduler());QuantaSealBulkEncryptBatch - internal logic
// QuantaSealBulkEncryptBatch.cls (excerpt - full source in package)
global class QuantaSealBulkEncryptBatch
implements Database.Batchable<SObject>, Database.AllowsCallouts {
private String objectName;
private String fieldName;
private String operation; // 'encrypt' or 'decrypt'
private String whereClause;
global QuantaSealBulkEncryptBatch(
String objectName, String fieldName, String operation, String whereClause
) {
this.objectName = objectName;
this.fieldName = fieldName;
this.operation = operation;
this.whereClause = whereClause;
}
global Database.QueryLocator start(Database.BatchableContext bc) {
String query = 'SELECT Id, ' + fieldName + ' FROM ' + objectName
+ ' ' + whereClause;
return Database.getQueryLocator(query);
}
global void execute(Database.BatchableContext bc, List<SObject> scope) {
List<SObject> toUpdate = new List<SObject>();
for (SObject record : scope) {
String fieldValue = (String) record.get(fieldName);
if (fieldValue == null) continue;
String result = (operation == 'encrypt')
? QuantaSealFieldEncryptionController.encryptFieldValue(fieldValue)
: QuantaSealFieldEncryptionController.decryptFieldValue(fieldValue);
record.put(fieldName, result);
toUpdate.add(record);
}
if (!toUpdate.isEmpty()) {
update toUpdate;
}
}
global void finish(Database.BatchableContext bc) {
AsyncApexJob job = [
SELECT Id, Status, NumberOfErrors, TotalJobItems
FROM AsyncApexJob WHERE Id = :bc.getJobId()
];
System.debug(
'Bulk ' + operation + ' complete. '
+ job.TotalJobItems + ' batches, '
+ job.NumberOfErrors + ' errors.'
);
}
}Governor limit reference
Outbound Proxy#
QuantaSealProxyService routes Salesforce outbound HTTP callouts through the QuantaSeal proxy engine. This means:
- Credentials (OAuth2 tokens, API keys) are never stored in Salesforce - they are unsealed from the QuantaSeal Vault on demand.
- All outbound traffic is encrypted with ML-KEM-768 + AES-256-GCM at the QuantaSeal layer before reaching the target API.
- Every callout is recorded in the immutable QuantaSeal audit hash chain.
- SSRF protection is enforced server-side - QuantaSeal blocks requests to private IP ranges even if Salesforce doesn't.
Basic proxy callout
// Route a callout through QuantaSeal to an external REST API
// The target API credentials are stored in the QuantaSeal Vault (not in Salesforce)
public class ExternalAPIService {
public static Map<String,Object> fetchCustomerData(String customerId) {
// Build the proxy request
Map<String,Object> proxyPayload = new Map<String,Object>{
'integration_id' => '550e8400-e29b-41d4-a716-446655440001', // QuantaSeal Integration UUID
'operation' => 'GET_CUSTOMER',
'payload' => new Map<String,Object>{
'customer_id' => customerId
}
};
HttpRequest req = new HttpRequest();
req.setEndpoint('callout:QuantaSeal_API/api/v2/proxy/outbound');
req.setMethod('POST');
req.setHeader('Content-Type', 'application/json');
req.setBody(JSON.serialize(proxyPayload));
Http h = new Http();
HttpResponse res = h.send(req);
if (res.getStatusCode() != 200) {
throw new CalloutException(
'QuantaSeal proxy failed [' + res.getStatusCode() + ']: ' + res.getBody()
);
}
// Response contains an encrypted envelope - QuantaSeal decrypts on the client side
// or you can return the envelope to be decrypted client-side via the SDK
Map<String,Object> body = (Map<String,Object>) JSON.deserializeUntyped(res.getBody());
Map<String,Object> data = (Map<String,Object>) body.get('data');
// If decryption is handled server-side, 'payload' contains the decrypted response
return (Map<String,Object>) data.get('payload');
}
}Calling SAP through the proxy
A complete example routing a Salesforce opportunity sync to SAP S/4HANA via the QuantaSeal proxy, using credentials stored in the QuantaSeal Vault:
// SAPIntegrationService.cls
public class SAPIntegrationService {
private static final String SAP_INTEGRATION_ID = '7c9e6679-7425-40de-944b-e07fc1f90ae7';
// Called from OpportunityTrigger.trigger on after insert
public static void syncOpportunityToSAP(List<Opportunity> opportunities) {
for (Opportunity opp : opportunities) {
try {
syncSingle(opp);
} catch (Exception e) {
System.debug('SAP sync failed for ' + opp.Id + ': ' + e.getMessage());
// Log failure to QuantaSeal_Error__c custom object
createErrorLog(opp.Id, e.getMessage());
}
}
}
private static void syncSingle(Opportunity opp) {
// Payload that SAP expects (mapped from Salesforce fields)
Map<String,Object> sapPayload = new Map<String,Object>{
'SalesDocument' => opp.Name,
'SoldToParty' => opp.AccountId,
'NetAmount' => opp.Amount,
'Currency' => opp.CurrencyIsoCode,
'SalesOrg' => 'AU01',
'RequestedDate' => String.valueOf(opp.CloseDate)
};
Map<String,Object> proxyRequest = new Map<String,Object>{
'integration_id' => SAP_INTEGRATION_ID,
'operation' => 'CREATE_SALES_ORDER',
'payload' => sapPayload,
'target_object' => 'SalesOrder'
};
HttpRequest req = new HttpRequest();
req.setEndpoint('callout:QuantaSeal_API/api/v2/proxy/outbound');
req.setMethod('POST');
req.setHeader('Content-Type', 'application/json');
req.setBody(JSON.serialize(proxyRequest));
req.setTimeout(30000); // 30s - SAP can be slow
Http h = new Http();
HttpResponse res = h.send(req);
if (res.getStatusCode() == 200) {
Map<String,Object> body = (Map<String,Object>) JSON.deserializeUntyped(res.getBody());
Map<String,Object> data = (Map<String,Object>) body.get('data');
String sapOrderId = (String) ((Map<String,Object>) data.get('payload')).get('SalesDocument');
// Write SAP order ID back to Salesforce
opp.SAP_Order_Id__c = sapOrderId;
} else {
throw new CalloutException('SAP proxy error: ' + res.getStatusCode());
}
}
private static void createErrorLog(Id oppId, String errorMsg) {
insert new QuantaSeal_Error__c(
Related_Record_Id__c = oppId,
Error_Message__c = errorMsg.left(255),
Timestamp__c = Datetime.now()
);
}
}integration_id in the proxy request is the UUID of the QuantaSeal Integration record, not a Salesforce record ID. Find it in the QuantaSeal Admin Console under Integrations, or via GET /api/v2/integrations. Each integration stores a reference to the vault credential (OAuth2 token, API key) it uses for authentication.Webhook Receive#
External systems (e.g. Stripe payment events, AWS SNS alerts, or partner API notifications) can send quantum-safe webhooks into Salesforce via QuantaSeal. QuantaSeal verifies the ML-DSA-65 + HMAC-SHA-512 signatures, decrypts the payload, and forwards the verified plaintext to a Salesforce Site.com endpoint or a connected Apex REST class.
Architecture
Salesforce Apex REST receiver
// QuantaSealWebhookReceiver.cls
// Expose this as a Salesforce Site.com endpoint: https://<your-site>.force.com/services/apexrest/quantaseal/webhook
@RestResource(urlMapping='/quantaseal/webhook/*')
global class QuantaSealWebhookReceiver {
@HttpPost
global static void handleInbound() {
RestRequest req = RestContext.request;
RestResponse res = RestContext.response;
try {
// QuantaSeal has already verified signatures before forwarding
// The X-QuantaSeal-Verified header confirms verification passed
String verified = req.headers.get('X-QuantaSeal-Verified');
if (verified != 'true') {
res.statusCode = 403;
res.responseBody = Blob.valueOf('{"error":"Signature verification required"}');
return;
}
// Payload is the decrypted plaintext JSON from QuantaSeal
String body = req.requestBody.toString();
Map<String,Object> event = (Map<String,Object>)
JSON.deserializeUntyped(body);
String eventType = (String) event.get('event_type');
Map<String,Object> payload = (Map<String,Object>) event.get('payload');
// Route to the correct handler based on event type
if (eventType == 'payment.succeeded') {
handlePaymentSucceeded(payload);
} else if (eventType == 'customer.created') {
handleCustomerCreated(payload);
} else {
System.debug('Unhandled QuantaSeal event type: ' + eventType);
}
res.statusCode = 200;
res.responseBody = Blob.valueOf('{"received":true}');
} catch (Exception e) {
System.debug('QuantaSeal webhook error: ' + e.getMessage());
res.statusCode = 500;
// Return generic error - do not expose internal details
res.responseBody = Blob.valueOf('{"error":"Internal error"}');
}
}
private static void handlePaymentSucceeded(Map<String,Object> payload) {
String accountId = (String) payload.get('salesforce_account_id');
Decimal amount = Decimal.valueOf(String.valueOf(payload.get('amount')));
insert new Payment__c(
Account__c = accountId,
Amount__c = amount,
Status__c = 'Received',
Source__c = 'QuantaSeal Webhook'
);
}
private static void handleCustomerCreated(Map<String,Object> payload) {
String email = (String) payload.get('email');
String name = (String) payload.get('name');
// Check for existing Contact by email before inserting
List<Contact> existing = [
SELECT Id FROM Contact WHERE Email = :email LIMIT 1
];
if (existing.isEmpty()) {
insert new Contact(
LastName = name.contains(' ') ? name.substringAfterLast(' ') : name,
FirstName = name.contains(' ') ? name.substringBeforeLast(' ') : '',
Email = email,
LeadSource = 'QuantaSeal Integration'
);
}
}
}Register the Salesforce endpoint in QuantaSeal
# Register your Salesforce Site endpoint as a webhook_adapter integration in QuantaSeal
curl -X POST https://api.quantaseal.io/api/v2/integrations \
-H "X-API-Key: qs_live_..." \
-H "Content-Type: application/json" \
-d '{
"name": "Salesforce Webhook Receiver",
"system_type": "webhook_adapter",
"config": {
"endpoint_url": "https://your-site.force.com/services/apexrest/quantaseal/webhook",
"method": "POST",
"headers": {
"Content-Type": "application/json",
"X-QuantaSeal-Verified": "true"
}
},
"allowed_operations": ["RECEIVE_WEBHOOK"]
}'Troubleshooting#
Common errors and their resolutions. For errors not listed here, check the QuantaSeal audit log at GET /api/v2/audit/logs?source=salesforceand the Salesforce Setup > Apex Jobs log.
System.CalloutException: Unauthorized endpointCause
api.quantaseal.io is not in the Salesforce Remote Site Settings allowlist.
Fix
Go to Setup > Security > Remote Site Settings and add https://api.quantaseal.io. The post-install script should do this automatically - if it did not, check QuantaSeal_Settings__mdt for a blank API Endpoint field.
HTTP 401 - {"error":"invalid_token"}Cause
The Named Credential access token is expired or the JWT Bearer exchange failed. This often happens when the Connected App certificate in Salesforce does not match the one uploaded to QuantaSeal.
Fix
Re-upload the same .crt file to both the Salesforce Connected App (Setup > App Manager > Edit > Use Digital Signatures) and to the QuantaSeal Admin Console (Settings > Salesforce Connected App). Then re-authenticate the Named Credential by editing it and checking 'Start Authentication Flow on Save'.
HTTP 403 - {"error":"Access denied"}Cause
The tenant derived from the JWT iss claim does not match the tenant_id in the QuantaSeal_Settings__mdt Custom Metadata record, or the Connected App Consumer Key is wrong.
Fix
In Salesforce, open Custom Metadata Types > QuantaSeal_Settings__mdt > Manage Records and verify that Tenant ID matches your UUID from app.quantaseal.io > Settings > Tenant. Verify Client ID matches the Connected App Consumer Key exactly (no trailing spaces).
HTTP 429 - Too Many RequestsCause
Rate limit exceeded for your QuantaSeal plan. Starter: 1,000 req/min. Professional: 5,000 req/min.
Fix
Add a retry with exponential backoff in your Apex callout code. For bulk jobs, reduce the batch size or add System.sleep() calls. If you are consistently hitting 429, upgrade your QuantaSeal plan or contact support for a custom rate limit.
QuantaSealBulkEncryptBatch - Too many callouts: 101Cause
The batch size is set above 100 for callout batches. Salesforce limits callout-enabled batch executions to 100 callouts per transaction.
Fix
Set the batch size to 100 or lower when calling Database.executeBatch(). The recommended size is 100 to match the governor limit exactly: Database.executeBatch(new QuantaSealBulkEncryptBatch(...), 100).
QuantaSealPostInstall did not create QuantaSeal_Settings__mdtCause
The post-install script requires Modify Metadata or Customize Application permission. If the installing user lacks this, the metadata record is not created.
Fix
Run the Setup Assistant manually: open the QuantaSeal app from the App Launcher, click Setup, and fill in the three fields. Alternatively, create the QuantaSeal_Settings__mdt record manually via Setup > Custom Metadata Types > QuantaSeal Settings > Manage Records > New.
LWC component not visible on Lightning pageCause
The QuantaSeal_User Permission Set has not been assigned to the current user, or the LWC component was not added to the Lightning page via App Builder.
Fix
Assign the QuantaSeal_User Permission Set to the user (Setup > Users > Permission Sets > QuantaSeal_User > Manage Assignments). Then drag the quantaseal__fieldEncryption component onto the Lightning Record Page in App Builder and save + activate.
Webhook receiver returns 403 - Signature verification requiredCause
The inbound webhook does not include the X-QuantaSeal-Verified: true header, which means the webhook did not pass through QuantaSeal's signature verification layer - it arrived directly at the Salesforce endpoint.
Fix
Ensure the external system sends webhooks to the QuantaSeal endpoint (https://api.quantaseal.io/api/v2/webhooks/receive/{tenant_id}), not directly to Salesforce. QuantaSeal verifies signatures and forwards verified events with the X-QuantaSeal-Verified header.
Need more help?
Check the QuantaSeal audit log for Salesforce-sourced events, open the Quanta Copilot assistant (Professional plan+) to diagnose issues conversationally, or contact support.