Platform

Security

PayForge implements defense-in-depth security controls covering encryption, authentication, authorization, and audit logging — aligned with PCI DSS 4.0 requirements.

0
Brakeman Warnings
0
Audit Vulnerabilities
AES-256
Encryption at Rest
JWT+MFA
Authentication

Authentication

PayForge uses JWT-based authentication with short-lived tokens. All API requests require a Bearer token in the Authorization header.

JWT Tokens

Stateless tokens signed with RS256. Configurable expiration with automatic refresh. Revocation via server-side denylist.

API Keys

Long-lived keys for server-to-server integration. Hashed with bcrypt before storage — plaintext keys are never stored.

Multi-Factor Auth

TOTP-based MFA for dashboard access. Required for sensitive operations like API key management and user administration.

Session Management

JWT revocation via denylist on sign-out. Automatic token expiry. Account lockout after 5 failed login attempts.

Encryption

Sensitive data is encrypted at multiple layers using industry-standard algorithms. Watch the encryption pipeline in action:

LayerMethodProtects
TransportTLS 1.3All API traffic in transit
ApplicationLockbox (AES-256-GCM)PII fields: email, card data, webhook URLs
SearchBlind Index (HMAC)Searchable encrypted field lookups
DatabasePostgreSQL encryptionAll data at rest on disk
SecretsRails encrypted credentialsAPI keys, signing keys, master keys
Application-level encryption with Lockboxruby
class Merchant < ApplicationRecord
  # PII encrypted with AES-256-GCM using Lockbox
  has_encrypted :email
  blind_index :email   # Enables searching encrypted fields

  # Webhook URL also encrypted (contains merchant secrets)
  has_encrypted :webhook_url
end

# Transparent usage — encryption/decryption is automatic
merchant = Merchant.find_by(email: "merchant@example.com")
# → Queries blind_index column, decrypts email on read

Authorization

Role-based access control is enforced at every endpoint using Action Policy. All resources are automatically scoped to the authenticated merchant — there is no way to access another merchant's data.

RolePermissions
ViewerRead transactions, view analytics and dashboards
ManagerAll viewer + process payments, manage webhooks, review fraud cases
AdminAll manager + manage API keys, users, merchant settings
Super AdminAll admin + manage merchants, system configuration, full CRUD

Rate Limiting

API requests are rate-limited using Rack::Attack with a sliding window counter to prevent abuse and ensure fair usage:

Standard API
100 req/min
Per API key
Authentication
5 req/min
Per IP address
Webhooks Test
10 req/min
Test endpoint
Rate limit response headershttp
X-RateLimit-Limit: 100
X-RateLimit-Remaining: 95
X-RateLimit-Reset: 1709726400

Security Headers

All responses include security headers configured via secure_headers:

Response security headershttp
Content-Security-Policy: default-src 'none'; frame-ancestors 'none'
Strict-Transport-Security: max-age=31536000; includeSubDomains
X-Content-Type-Options: nosniff
X-Frame-Options: DENY
X-XSS-Protection: 0
Referrer-Policy: strict-origin-when-cross-origin
Permissions-Policy: geolocation=(), camera=(), microphone=()

Audit Trail

Every mutation is tracked with PaperTrail versioning and domain events. The audit trail captures:

Who
Authenticated user ID
What
Action + changed data
When
Microsecond timestamp
Where
IP address + endpoint

Integration Best Practices

Use test keys in development

Test keys (sk_test_...) work identically to live keys but don't process real payments. Never use production keys in development.

Verify webhook signatures

Always verify the X-PayForge-Signature header before processing webhook events. See the Webhooks documentation for implementation examples.

Use idempotency keys

Include X-Idempotency-Key on all POST requests to prevent duplicate processing during retries or network issues.

Store keys securely

Use environment variables or a secrets manager. Never commit API keys to source control. Rotate keys regularly.

Implement exponential backoff

When rate-limited (429), back off exponentially. Start with 1 second and double on each retry, up to a maximum of 30 seconds.