Integrations
Integrate Vault
Vault holds the fields you would rather not have in your own database: access tokens for a customer's systems, identity documents, anything a security questionnaire asks about. Each customer gets their own data encryption key, and every decrypt leaves a record.
Before you begin
- A clear list of what belongs in Vault. Encrypting everything makes queries impossible; encrypt the fields whose exposure would be an incident.
- A key scope decided up front. Per organization is the usual answer, because it lets one customer's data be destroyed without touching another's.
- A place to store the returned object ID. Vault gives you an identifier; your row keeps that instead of the value.
- A read path that runs on a server. A decrypt call is never made from a browser.
Install
Every example on this page uses the Node.js client. The same calls exist in the Python, Go, Ruby, PHP and Java SDKs, following the naming conventions of each language.
1npm i @paycux/node
Configure
Vault uses your ordinary API key. If a customer requires their own key material, the key reference below points at their KMS key rather than the platform-managed one.
1PAYCUX_API_KEY=sk_example_7f4c1b9ad2e84c6f2PAYCUX_VAULT_KEY_SCOPE=organization3PAYCUX_VAULT_KEY_REF=paycux-managed
Then, in the dashboard:
- 1Open Vault and choose the key scope for the environment: one key per organization, or one key for the whole environment.
- 2Set the retention policy for objects, and whether a delete is immediate or held for a grace period.
- 3If a customer brings their own key, add their KMS key reference against their organization and confirm the grant works before you write anything under it.
- 4Turn on the audit stream for decrypt events. This is the record a security review will ask to see.
- 5Restrict which API keys may decrypt. Writing and reading are separate capabilities and most services only need one of them.
Implement
Store, read and delete. The value crosses the wire once on the way in and once on each read; in between, your database holds only an identifier.
Store a secret
Write the value against the organization it belongs to, then keep the returned object ID on your row. Metadata is not encrypted, so put searchable labels there and never the value itself.
1import { Paycux } from "@paycux/node";23const paycux = new Paycux(process.env.PAYCUX_API_KEY);45export async function storeToken(organizationId: string, provider: string, value: string) {6 const object = await paycux.vault.createObject({7 organizationId,8 name: provider + "-access-token",9 value,10 metadata: { provider, kind: "access_token" },11 });1213 await db.integration.update({14 where: { organizationId, provider },15 data: { vaultObjectId: object.id },16 });1718 return object.id;19}
Read it back
Decrypt at the moment of use and let the value go out of scope immediately. Do not cache it, do not log it, and do not put it in an error message.
1import { Paycux } from "@paycux/node";23const paycux = new Paycux(process.env.PAYCUX_API_KEY);45export async function withToken<T>(objectId: string, fn: (token: string) => Promise<T>) {6 const object = await paycux.vault.readObject({7 objectId,8 reason: "outbound_api_call",9 });1011 try {12 return await fn(object.value);13 } finally {14 object.dispose();15 }16}
Rotate and destroy
Rotation writes a new version and leaves the old one unreadable; deletion removes the object. When a customer leaves, destroying their key makes every object under it unrecoverable in one step.
1import { Paycux } from "@paycux/node";2import { requirePermission } from "@/lib/authz";34const paycux = new Paycux(process.env.PAYCUX_API_KEY);56export async function POST(request: Request) {7 await requirePermission("vault:manage");8 const { objectId, value } = await request.json();910 const object = await paycux.vault.updateObject({ objectId, value });1112 return Response.json({ id: object.id, version: object.version });13}
Verify
Two things need proving: that your own database no longer holds the plaintext, and that a decrypt is recorded.
- 1Store a value, then read the row directly out of your database. It must contain an object ID and no trace of the value.
- 2Read the value back through your server path and confirm it matches what you wrote.
- 3Check the audit stream and confirm the read appears with the actor, the object and the reason you passed.
- 4Attempt a decrypt with an API key that has no read capability and confirm it is refused.
- 5Rotate the object, then confirm the previous version can no longer be read while the current one can.
Reference
Request and response shapes for each of these live in the API reference. Paths are relative to https://api.paycux.com.
| Method | Path | What it does |
|---|---|---|
| POST | /vault/objects | Encrypt and store a value, returning an object ID. |
| GET | /vault/objects/:id | Decrypt and return a stored value. |
| PUT | /vault/objects/:id | Write a new version of an object. |
| DELETE | /vault/objects/:id | Destroy an object and every version of it. |
| GET | /vault/objects | List objects by organization or metadata, without values. |
| GET | /vault/keys | List the data encryption keys in the environment. |
| POST | /vault/keys/rotate | Rotate the key behind a scope, re-wrapping its objects. |