DISCLAIMER: This repository is provided for reference and exploratory purposes only. The code, configuration, and architecture contained herein are intended to illustrate integration patterns and are not designed, tested, or hardened for production use. Skyflow makes no representations or warranties regarding the suitability, security, or correctness of this implementation for any particular purpose. Use in production environments — or with real sensitive data — is done entirely at your own risk and is strongly discouraged without a thorough independent security review and adaptation to your organization's requirements.
A reference implementation showing how to integrate Skyflow with Salesforce to protect sensitive PII. On record save, PII fields are tokenized into a Skyflow vault before the record is committed to the Salesforce database. Tokens can be detokenized on demand, and the vault can be searched by plaintext value without ever exposing PII in Salesforce.
Install the latest version into your org with this URL:
https://login.salesforce.com/packaging/installPackage.apexp?p0=04tgK000000DUoHQAW
Package version:
Skyflow for Salesforce@1.0.0-9
Package ID:04tgK000000DUoHQAW
The Skyflow vault schema used by this implementation is included at docs/vault-schema.json. It defines five tables, each holding a single PII field with deterministic tokenization and masked redaction:
| Table | Field | Token type | Masked format |
|---|---|---|---|
first_name |
first_name |
Deterministic UUID | Joh****** |
last_name |
last_name |
Deterministic UUID | Smi****** |
email |
email |
Deterministic FPT | ****oe@example.com |
phone |
phone_number |
Deterministic FPT | 0412****** |
date_of_birth |
date_of_birth |
Deterministic FPT | **/**/1990 |
Import the schema via the Skyflow Studio vault import or the Management API.
When a record is created, a before insert trigger intercepts the plaintext PII, replaces it with a Tokenizing... placeholder in the database row, then queues an async callout that tokenizes each field and writes the resulting tokens back to the record.
sequenceDiagram
actor User
participant SF as Salesforce
participant Trigger as RC_CustomerTrigger<br/>(before insert)
participant Handler as RC_CustomerTriggerHandler
participant Skyflow as Skyflow Vault
User->>SF: Create record with PII fields
SF->>Trigger: before insert fires
Trigger->>Handler: onBeforeInsert(newRecords)
Handler->>Handler: Capture plaintext PII from record
Handler->>Handler: Replace PII fields with "Tokenizing..." placeholder
Handler-->>SF: Record committed with placeholder values
Handler->>Handler: Queue @future(callout=true) tokenizeAsync()
Note over Handler,Skyflow: Async — runs after DML commit
loop For each PII field
Handler->>Skyflow: POST /v1/vaults/{vault_id}/{table}<br/>{"records":[{"fields":{fieldName: value}}],"tokenization":true}
Skyflow-->>Handler: {"records":[{"tokens":{fieldName: "token-value"}}]}
end
Handler->>SF: Update record: set tokens, Tokenized__c = true
Handler->>User: Custom notification: "Customer Tokenized"
A user clicks "Reveal" on the record page. The LWC calls an Apex method that reads the stored tokens and exchanges them for plaintext values via the Skyflow detokenize API. A redaction level (PLAIN_TEXT, MASKED, or REDACTED) is passed by the caller.
sequenceDiagram
actor User
participant LWC as rcDetokenize LWC
participant Apex as RC_DetokenizeService
participant SF as Salesforce DB
participant Skyflow as Skyflow Vault
User->>LWC: Click "Reveal" (selects redaction level)
LWC->>Apex: detokenizeRecord(recordId, redaction)
Apex->>SF: SOQL — SELECT token fields WHERE Id = recordId
SF-->>Apex: Record with token values
Apex->>Skyflow: POST /v1/vaults/{vault_id}/detokenize<br/>{"detokenizationParameters":[{token, redaction}, ...]}
Skyflow-->>Apex: {"records":[{token, value}, ...]}
Apex->>Apex: Map tokens → field names
Apex-->>LWC: {firstName, lastName, email, phone, dateOfBirth}
LWC-->>User: Display plaintext (or masked) values
A user enters one or more plaintext search terms. For each term the Apex service queries the vault to find matching tokens, then uses those tokens as SOQL filter values to find Salesforce records — no plaintext ever touches the Salesforce database.
sequenceDiagram
actor User
participant LWC as rcVaultSearch LWC
participant Apex as RC_VaultSearch
participant Skyflow as Skyflow Vault
participant SF as Salesforce DB
User->>LWC: Enter search term(s) and submit
LWC->>Apex: search(firstName, lastName, email, phone, dateOfBirth)
loop For each non-blank search field
Apex->>Skyflow: POST /v1/vaults/{vault_id}/{table}/query<br/>{"query":"SELECT skyflow_id FROM {table} WHERE {field} ILIKE '%value%' LIMIT 25"}
Skyflow-->>Apex: {"records":[{skyflow_id}, ...]}
Apex->>Skyflow: GET /v1/vaults/{vault_id}/{table}?skyflow_ids=...&tokenization=true
Skyflow-->>Apex: {"records":[{"tokens":{fieldName: "token"}},...]}
Apex->>Apex: Collect matching tokens into a Set
end
Apex->>SF: SOQL — SELECT ... WHERE Field__c IN :tokens (AND across all searched fields)
SF-->>Apex: Matching records (token values only)
Apex-->>LWC: List of matching records
LWC-->>User: Display results table
- Salesforce org (Developer Edition, sandbox, or scratch org)
- Salesforce CLI installed
- A Skyflow vault provisioned with the schema in
docs/vault-schema.json - A Skyflow bearer token or service account API key
In Setup > Named Credentials, open Skyflow Retail Vault and update the URL to point to your vault:
https://<YOUR_CLUSTER_ID>.vault.skyflowapis.com/v1/vaults/<YOUR_VAULT_ID>
All Skyflow API calls are made from Apex via Named Credentials, so no Remote Site Setting is required.
In Setup > Named Credentials > External Credentials, open Skyflow_Retail_Auth and update the Authorization header value:
Bearer <YOUR_SKYFLOW_BEARER_TOKEN>
You can use either a short-lived bearer token or a service account API key. To generate a bearer token from a service account JWT:
curl -X POST https://manage.skyflowapis.com/v1/auth/sa/oauth/token \
-H "Content-Type: application/json" \
-d '{"grant_type":"urn:ietf:params:oauth:grant-type:jwt-bearer","assertion":"<YOUR_SIGNED_JWT>"}'Alternatively, create a service account with an API key in Skyflow Studio (Manage Account > Service accounts > Add service account) and use the API key directly as the bearer value.
Assign the Skyflow for Salesforce permission set to any user who needs access.
Via CLI:
sf org assign permset --name Skyflow_for_Salesforce --target-org <your-org-alias>Via Setup UI:
- Go to Setup > Users > Users
- Click the user's name
- Scroll to the Permission Set Assignments related list and click Edit Assignments
- Move Skyflow for Salesforce from Available to Enabled, then click Save
Full guide: Salesforce CLI Setup Guide
# macOS via Homebrew
brew install sf
# Or download from developer.salesforce.com/tools/salesforce-cli# Production / Developer Edition
sf org login web --alias my-org
# Sandbox
sf org login web --alias my-sandbox --instance-url https://test.salesforce.com# Deploy all source
sf project deploy start --target-org my-org
# Deploy using the manifest
sf project deploy start --manifest manifest/package.xml --target-org my-org
# Check deploy status
sf project deploy report --target-org my-org