API Reference

Webhooks

Configure webhook endpoints, the card.issued.secrets event, JWE encrypted card secrets, and delivery acknowledgment.

Webhooks

Dotva delivers webhook events to your configured HTTP endpoints. Currently one event is available for the server-to-server API: card.issued.secrets, which delivers the encrypted PAN / CVV / expiry after a card is successfully issued.


card.issued.secrets

After a card is issued, if the workspace has configured an RSA public key and has an endpoint subscribed to card.issued.secrets, the platform delivers this webhook.

Encrypted card secrets are delivered only via this webhook — they never appear in regular API responses.

Payload

{
  "event": "card.issued.secrets",
  "workspace_id": 12,
  "data": {
    "card_id": 345,
    "card_uuid": "8f1c...",
    "card_last4": "4242",
    "card_bin": "411111",
    "secrets_jwe": "<JWE compact string>",
    "key_fingerprint": "sha256:9c4d..."
  }
}
FieldDescription
secrets_jweJWE compact serialization (RSA-OAEP-256 + A256GCM). Decrypt with your private key to obtain PAN / CVV / expiry.
key_fingerprintSHA-256 fingerprint of the public key used to encrypt, so you know which private key to use for decryption.

Delivery & acknowledgment

  • Reply 200 (any body) to acknowledge receipt.
  • Non-200 responses trigger retries with exponential back-off.
  • Duplicate deliveries are possible — make your handler idempotent (use card_uuid as the deduplication key).

Setting up

  1. Upload your RSA public key in Developer Settings → Webhook Keys.
  2. Register your endpoint URL and subscribe it to card.issued.secrets.
  3. After issuing a card via POST /cards, wait for the webhook delivery (typically within seconds of the order completing).

Decrypting secrets_jwe

The JWE uses RSA-OAEP-256 for key encryption and A256GCM for content encryption. Most modern crypto libraries support this out of the box.

Node.js example (using jose):

import { compactDecrypt } from 'jose'
import { createPrivateKey } from 'crypto'

const privateKey = createPrivateKey({ key: process.env.PRIVATE_KEY_PEM })
const { plaintext } = await compactDecrypt(secrets_jwe, privateKey)
const secrets = JSON.parse(new TextDecoder().decode(plaintext))
// secrets.pan, secrets.cvv, secrets.expiry_month, secrets.expiry_year