What needs protection
A partner integration typically handles four classes of secrets and three classes of sensitive data:| Class | Examples |
|---|---|
| Virtuous API tokens | One per customer organization. Grants the integration’s full configured permission set. |
| Webhook secrets | One per customer per webhook subscription. Used to verify incoming events. |
| Source-platform credentials | Per-customer OAuth tokens or API keys for Stripe, Mailchimp, etc. |
| Integration-internal secrets | Database credentials, infrastructure secrets, internal service tokens. |
| Donor PII | Names, addresses, emails, phone numbers passing through the integration. |
| Financial data | Gift amounts, payment methods (typically tokenized), giving history. |
| Health and demographic data | Some donor records include sensitive attributes. Handle with extra care. |
Storing Virtuous API tokens
Virtuous API tokens are the integration’s primary credential. A leaked token can be used to read or write any data the token’s permission set grants — including donor PII, financial history, and webhook configuration.Storage
Store tokens in a dedicated secrets manager. The acceptable options:| Option | Use |
|---|---|
| Cloud-provider secrets manager (AWS Secrets Manager, GCP Secret Manager, Azure Key Vault) | The default for most cloud deployments. Integrates with IAM, audit logging, and rotation primitives. |
| Self-hosted secrets manager (Vault, Bitwarden Secrets Manager) | For environments not running in a cloud provider. |
| Encrypted database column with rotated KMS-managed key | Acceptable when secrets manager isn’t available. Requires careful KMS key rotation. |
| Anti-pattern | Why |
|---|---|
| Environment variables in deployment manifests | Visible to anyone with deploy access; logged in deployment system; not auditable. |
.env files in source control | Visible in git history forever; impossible to revoke. |
| Plaintext database columns | Anyone with read access to the database has all customer tokens. |
| Hardcoded in source | Compromised on every code commit, every CI artifact, every container image. |
Naming
Use a key naming convention that makes the secret’s scope obvious:Access control
The principle: only the workers that need a customer’s token can access it. Three rules:| Rule | Why |
|---|---|
| Per-customer access scoping. The submitter for customer A can read customer A’s token but not customer B’s. | Limits blast radius of a compromised worker. |
| No human read access in production. Engineers should never need to view a customer’s plaintext token to debug. | Tokens leak through screenshots, terminal scrollback, support tickets. |
| All access audit-logged. Every read of a customer’s token produces an audit log entry with the accessor, timestamp, and reason. | Detects misuse; supports incident response. |
Rotation
API tokens should be rotated periodically. The cadence depends on the integration’s threat model — typical recommendations are every 90 days for high-sensitivity integrations, every 6–12 months for lower-sensitivity ones. The rotation flow:Generate a new token in Virtuous
Add the new token alongside the existing one
Cut over reads
Validate
Storing webhook secrets
Webhook secrets are different from API tokens in one important way: the integration generates them rather than receiving them from Virtuous. When you callPOST /api/Webhook to subscribe, you provide the secret field.
Generation
Generate webhook secrets with a cryptographically secure random source — at least 32 bytes of entropy:Storage
The same secrets manager that holds API tokens holds webhook secrets. Use a parallel naming convention:Rotation
Webhook secret rotation is more complex than API token rotation because the verifier must accept both old and new during a rotation window. See Webhooks Overview: Rotating the secret for the pattern.OAuth and source-platform credentials
For source platforms that use OAuth (Mailchimp, Constant Contact, Stripe Connect, etc.), the integration typically holds:- A long-lived refresh token that can produce fresh access tokens.
- Short-lived access tokens issued from the refresh token.
Refresh token storage
Refresh tokens are the long-term credential — protect them like API tokens. Store in the secrets manager:Access token handling
Access tokens are short-lived. Cache them in memory or a short-TTL cache (e.g., Redis with the token’s natural expiration as TTL). Don’t store access tokens in the secrets manager — they expire and the constant write churn is wasteful.Revoked credentials
Customers can revoke a partner integration’s OAuth grant from the source platform’s dashboard. When this happens, refresh attempts return errors. Detect and surface:Network and transport security
A few practices outside of secret storage:TLS everywhere
Every external API call uses HTTPS with a valid TLS certificate from a public CA. Reject endpoints that present invalid certificates or use plain HTTP.Egress IP allowlisting
Some customers want to allowlist the egress IPs your integration uses to talk to Virtuous. Plan to support this:- Run your workers behind a NAT gateway with a stable IP, or use a cloud-provider feature that produces stable egress IPs.
- Document the egress IPs in your customer documentation so customer firewall teams can allowlist them.
- Notify customers in advance when egress IPs change.
Webhook receiver hardening
Your webhook receiver is internet-facing. Standard web-application hardening applies:- HTTPS-only with a valid certificate.
- Rate limiting at the edge to prevent abuse.
- A WAF or similar to filter obvious attacks.
- Signature verification on every incoming request (see Signature Verification).
- Reject requests with unexpected Content-Type or oversize bodies.
PII and sensitive data handling
Beyond credentials, the integration handles a lot of donor PII. Treat it accordingly.Don’t log PII
Avoid logging fields that contain donor identifiers. Structured logs should reference donors by stable identifiers (Virtuous Contact ID, your platform’s user ID) rather than PII.Don’t log request bodies in error paths
A common pattern that leaks PII:PII in error responses
If your integration’s own API surface returns errors to your customers’ developers, scrub PII from error messages. A400 Bad Request response should explain what was wrong with the schema, not echo back the donor data that was rejected.
Retention
Establish retention policies for sync state and dead-letter records:| Data | Suggested retention |
|---|---|
Active sync state (partner_contacts, partner_gifts) | Indefinite while the customer is active |
| Dead-letter entries | 90 days after resolution; archive thereafter |
| Reconciliation logs | 1 year; archive thereafter |
| Worker logs containing identifiers | 30 days hot, 1 year cold |
| Webhook delivery captures | 30 days |
Customer onboarding and offboarding
The credential lifecycle starts at onboarding and ends at offboarding. Both need explicit handling.Onboarding
When a customer connects your integration:Receive their Virtuous API token securely
GET /api/Health or equivalent) before storing.Store in the secrets manager
Configure webhook subscription
POST /api/Webhook with the customer’s payload URL pointing at your integration’s endpoint.Initial reconciliation
Offboarding
When a customer disconnects:Stop syncing immediately
Delete the webhook subscription in Virtuous
DELETE /api/Webhook/{webhookId} so Virtuous stops attempting deliveries to your endpoint.Revoke the customer's API token
Delete the customer's secrets
Delete or anonymize the customer's data
Permissions: least privilege
Virtuous API tokens carry the permission set configured on the underlying API key. The principle: request only the permissions your integration actually uses. If your integration only writes Gifts and reads Contacts, request a permission set that allows those operations and nothing else. Don’t request “all access” if you only need a subset — a compromised token with narrower permissions is less damaging than one with broader permissions. Document the permission requirements in your customer-facing setup documentation so customers can configure their API keys appropriately.Incident response
Plan for the credential-incident scenarios before they happen:Suspected credential compromise
A customer reports unexpected data in their Virtuous account that might trace to your integration’s writes:Rotate the customer's API token immediately
Audit the integration's recent writes
Check audit logs for unexpected token access