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..."
}
}
| Field | Description |
|---|---|
secrets_jwe | JWE compact serialization (RSA-OAEP-256 + A256GCM). Decrypt with your private key to obtain PAN / CVV / expiry. |
key_fingerprint | SHA-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-
200responses trigger retries with exponential back-off. - Duplicate deliveries are possible — make your handler idempotent (use
card_uuidas the deduplication key).
Setting up
- Upload your RSA public key in Developer Settings → Webhook Keys.
- Register your endpoint URL and subscribe it to
card.issued.secrets. - 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