Skip to main content

Security Model

The ItPay Protocol is designed with defense in depth — every API call is authenticated, every payload is verified, and the protocol never touches merchant funds. This page details the cryptographic foundations, anti-abuse measures, and data protection policies that secure the protocol.


1. HMAC-SHA256 Signature Verification

Every API request to the ItPay Gateway must carry an Authorization header proving the caller possesses a valid API key secret.

Understanding HMAC-SHA256 Signatures

HMAC-SHA256 (Hash-based Message Authentication Code with SHA-256) provides a way to verify both the authenticity and integrity of a message using a shared secret key. Unlike simple hashing, HMAC ensures that only parties possessing the secret can generate valid signatures, making it resistant to length-extension attacks and other cryptographic weaknesses.

The signing payload includes the request's timestamp, HTTP method, path, and body — every component that identifies the intent of the request is bound to the signature.

Request Signing

The signature is computed as:

Signature = HMAC-SHA256(
timestamp + method + path + body,
api_key_secret
)
Best Practice

Always include the raw request body in the signing payload. Never sign a pre-parsed or reformatted version of the body, as whitespace differences can produce a different signature.

Components:

ComponentValueExample
timestampUnix epoch seconds as a 10-digit string1745712345
methodUppercase HTTP methodPOST
pathURL path (no query string)/v1/payment-intents
bodyRaw request body (empty string for GET/DELETE){"amount":500,"currency":"USD"}

How Signature Verification Works in Practice

When an agent sends a payment request to the ItPay Gateway, the agent creates an HMAC-SHA256 signature using its secret API key. The gateway looks up the agent's stored secret, recomputes the HMAC independently, and compares the two. A matching signature proves the agent possesses the secret key and that the request payload hasn't been altered in transit.

Signing Swimlane

The diagram below shows the complete request-signing lifecycle from the Agent composing the request through ItPay's verification and processing.

sequenceDiagram
participant Agent as Agent (API Consumer)
participant ItPay as ItPay Gateway
actor DB as Key Store

Agent->>Agent: Compose HTTP request (method, path, body)
Agent->>Agent: Read api_key_secret from env/config
Agent->>Agent: Compute timestamp = unix_epoch_seconds
Agent->>Agent: Build signing payload = timestamp + method + path + body
Agent->>Agent: signature = HMAC-SHA256(payload, api_key_secret)
Agent->>ItPay: POST /v1/payment-intents
Agent->>ItPay: Authorization: ItPay {agent_id}:{signature}
Agent->>ItPay: X-ItPay-Timestamp: {timestamp}
ItPay->>DB: Lookup stored secret for agent_id
DB-->>ItPay: api_key_secret (bcrypt-verified)
ItPay->>ItPay: Recompute HMAC-SHA256(timestamp + method + path + body, secret)
ItPay->>ItPay: Compare computed == received signature
alt Signature matches
ItPay->>ItPay: Process request
ItPay-->>Agent: 200 OK / 201 Created
else Signature mismatch
ItPay-->>Agent: 401 Unauthorized + error code
end

Header Format

Authorization: ItPay {agent_id}:{signature}
X-ItPay-Timestamp: {unix_timestamp}
  • agent_id — your public agent identifier (e.g. agent_abc123)
  • signature — hex-encoded HMAC-SHA256 output
  • X-ItPay-Timestamp — the same Unix timestamp used in signing

Python Example

import hashlib
import hmac
import time

def sign_request(
api_key_secret: str,
method: str,
path: str,
body: str = "",
) -> tuple[str, str]:
"""Return (signature_hex, timestamp)."""
timestamp = str(int(time.time()))
payload = timestamp + method.upper() + path + body

signature = hmac.new(
key=api_key_secret.encode("utf-8"),
msg=payload.encode("utf-8"),
digestmod=hashlib.sha256,
).hexdigest()

return signature, timestamp

# Usage
secret = "sk_liv...f456"
sig, ts = sign_request(secret, "POST", "/v1/payment-intents", '{"amount":500,"currency":"USD"}')

headers = {
"Authorization": f"ItPay agent_abc123:{sig}",
"X-ItPay-Timestamp": ts,
"Content-Type": "application/json",
}

TypeScript Example

import { createHmac } from "node:crypto";

function signRequest(
apiKeySecret: string,
method: string,
path: string,
body: string = ""
): { signature: string; timestamp: string } {
const timestamp = String(Math.floor(Date.now() / 1000));
const payload = timestamp + method.toUpperCase() + path + body;

const signature = createHmac("sha256", apiKeySecret)
.update(payload)
.digest("hex");

return { signature, timestamp };
}

// Usage
const secret = "sk_liv...f456";
const { signature, timestamp } = signRequest(
secret,
"POST",
"/v1/payment-intents",
JSON.stringify({ amount: 500, currency: "USD" })
);

const headers = {
Authorization: `ItPay agent_abc123:${signature}`,
"X-ItPay-Timestamp": timestamp,
"Content-Type": "application/json",
};

Server-Side Verification

The gateway verifies every request by re-computing the HMAC with the stored secret for the claimed agent_id. If the signatures don't match — or the timestamp is outside the accepted window — the request is rejected with HTTP 401 Unauthorized.

Security Critical

The gateway stores only a bcrypt hash of API key secrets. The raw secret can never be retrieved — it can only be verified against the hash. If a secret is compromised, it must be rotated through the ItPay dashboard.

Error Scenarios

ScenarioConditionHTTP StatusError CodeResponse Body
Invalid signatureHMAC does not match401 Unauthorizedinvalid_signature{"error":"invalid_signature","message":"Signature verification failed"}
Expired timestampX-ItPay-Timestamp more than 5 min in the past401 Unauthorizedexpired_timestamp{"error":"expired_timestamp","message":"Timestamp is outside the accepted window"}
Premature timestampX-ItPay-Timestamp more than 5 min in the future401 Unauthorizedpremature_timestamp{"error":"premature_timestamp","message":"Timestamp is in the future beyond the allowed skew"}
Missing AuthorizationNo Authorization header401 Unauthorizedmissing_authorization{"error":"missing_authorization","message":"Authorization header is required"}
Missing TimestampNo X-ItPay-Timestamp header401 Unauthorizedmissing_timestamp{"error":"missing_timestamp","message":"X-ItPay-Timestamp header is required"}
Unknown agent_idagent_id in header does not exist401 Unauthorizedunknown_agent{"error":"unknown_agent","message":"Agent not found"}

Example: Rejected Invalid Signature

HTTP/1.1 401 Unauthorized
Content-Type: application/json

{
"error": "invalid_signature",
"message": "Signature verification failed",
"request_id": "req_abc123"
}

2. Anti-Replay Protection

ItPay prevents replay attacks through a combination of time-bounded windows, nonce verification, and idempotency guarantees.

Understanding Anti-Replay Protection

A replay attack occurs when an attacker intercepts a valid API request and retransmits it to trick the server into performing the same action again — such as processing a duplicate payment. ItPay's anti-replay system uses three complementary layers to eliminate this risk:

  1. Timestamp window — Limits the time frame in which any request is valid
  2. Cryptographic nonce — Ensures each request is unique, even within the valid time window
  3. Idempotency keys — Guarantees at-most-once execution regardless of delivery attempts

How Anti-Replay Protection Works in Practice

When an agent sends a payment request, it includes a unique nonce along with its HMAC signature. The ItPay Gateway first verifies the signature, then checks that the timestamp falls within the 5-minute window. Finally, it records the nonce in Redis with a SETNX command — if the nonce already exists, the request is rejected as a replay. This means even if an attacker captures the exact request and retransmits it within seconds, it will be rejected.

Anti-Replay Swimlane

sequenceDiagram
participant Agent as Agent (API Consumer)
participant ItPay as ItPay Gateway
participant Redis as Redis Cache

Agent->>Agent: Compute timestamp (unix epoch)
Agent->>Agent: Generate nonce (crypto.randomUUID)
Agent->>Agent: Sign payload = timestamp + nonce + method + path + body
Agent->>ItPay: POST /v1/payment-intents
Agent->>ItPay: Authorization: ItPay {agent_id}:{sig}
Agent->>ItPay: X-ItPay-Timestamp: {timestamp}
Agent->>ItPay: X-ItPay-Nonce: {nonce}
ItPay->>ItPay: Verify signature (see Section 1)
ItPay->>ItPay: Check |server_time - timestamp| <= 300 seconds
alt Timestamp outside 5-min window
ItPay-->>Agent: 401 Unauthorized (expired_timestamp)
else Timestamp valid
ItPay->>Redis: SETNX nonce:{nonce} "seen" EX 300
Redis-->>ItPay: 1 (first time) or 0 (already seen)
alt Nonce already used (replay)
ItPay-->>Agent: 409 Conflict (replay_detected)
else Fresh nonce
ItPay->>ItPay: Process request
ItPay-->>Agent: 200 OK
end
end

Timestamp Window

Every signed request is valid only within a 5-minute window around its X-ItPay-Timestamp:

  • Requests with timestamps more than 5 minutes in the past are rejected as expired
  • Requests with timestamps more than 5 minutes in the future are rejected as premature
  • Clock skew between client and server is automatically accounted for within this window

Nonce Verification

Each state-mutating request carries a unique nonce to prevent replay within the timestamp window:

X-ItPay-Nonce: {cryptographically_random_uuid}
  • The nonce is passed through SETNX in Redis with a TTL matching the timestamp window
  • If a nonce has already been seen within the TTL, the request is rejected as a replay
  • Nonces are generated client-side using crypto.randomUUID() (Node.js) or os.urandom (Python)

Idempotency via Redis SETNX

State-mutating endpoints (creating PaymentIntents, processing callbacks) use Redis SETNX to guarantee at-most-once execution:

  • The payment_intent_id is used as the idempotency key
  • SETNX returns success only if the key has not been processed before
  • The key expires after the PaymentIntent's lifecycle completes
> SETNX payment_intent:pay_2xK3m9QrL8vN5pW1 "processing" EX 3600
(integer) 1 → first time, proceed
> SETNX payment_intent:pay_2xK3m9QrL8vN5pW1 "processing" EX 3600
(integer) 0 → already processed, return cached result

This ensures that even if a callback webhook is delivered multiple times (e.g. due to network retries), the same payment_intent_id callback is processed exactly once.

Python Nonce Example

import os
import hashlib
import hmac
import time

def sign_request_with_nonce(
api_key_secret: str,
method: str,
path: str,
body: str = "",
) -> tuple[str, str, str]:
timestamp = str(int(time.time()))
nonce = os.urandom(16).hex() # 32-char hex nonce
payload = timestamp + nonce + method.upper() + path + body

signature = hmac.new(
key=api_key_secret.encode("utf-8"),
msg=payload.encode("utf-8"),
digestmod=hashlib.sha256,
).hexdigest()

return signature, timestamp, nonce

TypeScript Nonce Example

import { createHmac, randomUUID } from "node:crypto";

function signRequestWithNonce(
apiKeySecret: string,
method: string,
path: string,
body: string = ""
): { signature: string; timestamp: string; nonce: string } {
const timestamp = String(Math.floor(Date.now() / 1000));
const nonce = randomUUID();
const payload = timestamp + nonce + method.toUpperCase() + path + body;

const signature = createHmac("sha256", apiKeySecret)
.update(payload)
.digest("hex");

return { signature, timestamp, nonce };
}

Error Scenarios

ScenarioConditionHTTP StatusError CodeResponse Body
Replay attackNonce already seen in cache409 Conflictreplay_detected{"error":"replay_detected","message":"This request has already been processed"}
Expired requestServer time - timestamp > 300s401 Unauthorizedexpired_timestamp{"error":"expired_timestamp","message":"Timestamp is outside the accepted window"}
Duplicated idempotency keySETNX returns 0 for PaymentIntent200 OK (cached)Returns previously stored result

3. QR Security

QR codes are the primary human-facing payment interface. ItPay employs multiple layers of protection to prevent QR-based fraud.

Dynamic QR Codes

Every QR is dynamically generated per payment request — never static or pre-printed. The QR encodes a unique, signed payload:

itpay://pay/pay_2xK3m9QrL8vN5pW1?channel=alipay&amount=5.00&currency=USD&sig=hmac_hex
Best Practice

Always display a dynamic QR code on-screen rather than printing static codes. A static QR cannot be updated with new signatures or expiry times, making it vulnerable to replay and tampering.

Short Expiry (15 Minutes)

PropertyValue
QR lifetime15 minutes from generation
Post-expiry behaviorGateway rejects the QR; payer must request a new one
RationaleLimits the window for QR theft, screenshot reuse, or MitM attacks

In-Channel Signature

The QR data carries an embedded HMAC-SHA256 signature that the payment channel verifies before presenting the checkout screen. This prevents tampering with the amount, currency, or destination after the QR is generated.

Single-Use Consumption

Once a QR is scanned and the associated PaymentIntent transitions to succeeded, the QR is permanently invalidated. Any subsequent attempt to scan the same QR returns an error.


4. Webhook Verification

ItPay signs every webhook payload with your registered webhook secret so you can verify the delivery came from ItPay and was not tampered with.

Understanding Webhook Verification

Webhook endpoints are publicly accessible by nature, making them a target for attackers who may attempt to forge callback notifications. ItPay's webhook signature scheme ensures your application can cryptographically verify that incoming webhooks genuinely originated from the ItPay Gateway and have not been modified in transit.

The scheme uses a timestamp + payload signing approach that prevents both forgery and replay attacks on your webhook endpoint.

How Webhook Verification Works in Practice

When a payment settlement event occurs, the ItPay Gateway computes an HMAC-SHA256 signature over the raw JSON body prepended with a timestamp. This signature is sent in the X-ItPay-Signature header. Your application retrieves its stored webhook secret, recomputes the HMAC using the same raw body and timestamp, and compares the result using a constant-time comparison function. A match confirms the webhook is authentic and timely.

Webhook Verification Swimlane

sequenceDiagram
participant ItPay as ItPay Gateway
participant Agent as Agent (Webhook Receiver)
actor Store as Webhook Secret Store

Note over ItPay: Payment settlement event occurs
ItPay->>ItPay: Compute timestamp t = now()
ItPay->>ItPay: signed_payload = t + "." + raw_json_body
ItPay->>ItPay: signature = HMAC-SHA256(signed_payload, webhook_secret)
ItPay->>Agent: POST /webhooks/payment
ItPay->>Agent: X-ItPay-Signature: t={t},v1={signature}
ItPay->>Agent: Content-Type: application/json
Agent->>Store: Retrieve stored webhook_secret
Store-->>Agent: webhook_secret
Agent->>Agent: Parse X-ItPay-Signature header (t, v1)
Agent->>Agent: Check |now - t| <= max_age (300s default)
alt Timestamp expired
Agent-->>ItPay: Reject (ignore or log warning)
else Timestamp valid
Agent->>Agent: signed_payload = t + "." + raw_body
Agent->>Agent: computed = HMAC-SHA256(signed_payload, secret)
Agent->>Agent: constant_time_compare(computed, v1)
alt Signatures match
Agent->>Agent: Process webhook event
Agent-->>ItPay: 200 OK
else Signature mismatch
Agent-->>ItPay: 400 Bad Request (ignore event)
end
end

Signature Header

X-ItPay-Signature: t=1745712345,v1=abc123def456...
  • t — Unix timestamp of when the webhook was sent
  • v1 — hex-encoded HMAC-SHA256 of the raw JSON body

Verification Algorithm

  1. Extract the t and v1 values from the X-ItPay-Signature header
  2. Compute the expected signature: HMAC-SHA256(t + "." + raw_body, webhook_secret)
  3. Compare the computed signature against v1 using constant-time comparison

Constant-Time Comparison

Always use a constant-time comparison function to prevent timing side-channel attacks:

import hashlib
import hmac

def verify_webhook_signature(
raw_body: bytes,
signature_header: str,
webhook_secret: str,
max_age_seconds: int = 300,
) -> bool:
"""
Verify a webhook signature. Returns True if valid, False otherwise.
"""
parts = dict(
item.split("=", 1)
for item in signature_header.split(",")
)

timestamp = parts.get("t")
expected_sig = parts.get("v1")

if not timestamp or not expected_sig:
return False

# Reject webhooks older than max_age_seconds
if int(time.time()) - int(timestamp) > max_age_seconds:
return False

signed_payload = f"{timestamp}.{raw_body.decode('utf-8')}"

computed_sig = hmac.new(
key=webhook_secret.encode("utf-8"),
msg=signed_payload.encode("utf-8"),
digestmod=hashlib.sha256,
).hexdigest()

# Constant-time comparison — do NOT use ==
return hmac.compare_digest(computed_sig, expected_sig)
import { createHmac, timingSafeEqual } from "node:crypto";

function verifyWebhookSignature(
rawBody: string,
signatureHeader: string,
webhookSecret: string,
maxAgeSeconds: number = 300
): boolean {
const parts = Object.fromEntries(
signatureHeader.split(",").map((p) => p.split("=", 2))
);

const timestamp = parts["t"];
const expectedSig = parts["v1"];

if (!timestamp || !expectedSig) return false;

// Reject expired webhooks
if (Math.floor(Date.now() / 1000) - Number(timestamp) > maxAgeSeconds) {
return false;
}

const signedPayload = `${timestamp}.${rawBody}`;

const computedSig = createHmac("sha256", webhookSecret)
.update(signedPayload)
.digest("hex");

// Constant-time comparison
return timingSafeEqual(
Buffer.from(computedSig),
Buffer.from(expectedSig)
);
}
Security Critical

Never use standard string comparison (==) to verify webhook signatures. Python's hmac.compare_digest() or Node.js's timingSafeEqual() perform constant-time comparison that prevents attackers from inferring the signature byte-by-byte through timing measurements.

Webhook Error Scenarios

ScenarioConditionActionLogged As
Missing signature headerNo X-ItPay-Signature headerReject webhookmissing_webhook_signature
Malformed signatureHeader cannot be parsedReject webhookmalformed_webhook_signature
Expired webhookTimestamp exceeds max_ageReject webhookexpired_webhook
Signature mismatchHMAC does not matchReject webhookinvalid_webhook_signature
Unknown webhook secretAgent rotates secret; old signature failsReject until secret updatedunknown_webhook_secret

5. Fund Safety (不碰钱)

ItPay follows a strict no-fund-holding (不碰钱) architecture. The protocol never touches, pools, or controls merchant funds at any point in the payment lifecycle.

Understanding Fund Safety

The "不碰钱" (bù pèng qián — "don't touch the money") principle is the most important design constraint of the ItPay Protocol. Unlike payment aggregators (Stripe, Square) that collect funds into a merchant account and then disburse them, ItPay never intermediates funds at any stage. The gateway generates payment intents, tracks state, and provides reconciliation data — but the actual movement of money happens entirely between the payer's wallet and the merchant's account through the payment channel.

How Fund Safety Works in Practice

When a payer scans an ItPay-generated QR code, their payment app communicates directly with the payment channel (Alipay, WeChat, PromptPay). The channel verifies the QR's integrity independently using the embedded HMAC, then settles funds directly into the merchant's linked account. ItPay receives only a settlement callback notification — the money itself never passes through ItPay's infrastructure. This eliminates counterparty risk, regulatory burden, and the need for a money transmitter license.

Fund-Flow Diagram

The diagram below traces exactly how money moves — and where ItPay sits purely as a reconciliation layer.

sequenceDiagram
participant Payer as Payer Wallet
participant Channel as Payment Channel<br/>(Alipay/WeChat/PromptPay)
participant ItPay as ItPay Gateway<br/>(Reconciliation Only)
participant Merchant as Merchant Account

Note over Payer,Merchant: Money NEVER flows through ItPay

ItPay->>Payer: 1. Generate & present QR code
Payer->>Channel: 2. Scan QR, approve payment in app
Channel->>Channel: 3. Verify QR integrity (HMAC check)
Channel->>Merchant: 4. Settle funds directly to merchant
Channel->>ItPay: 5. Settlement callback (webhook)
ItPay->>ItPay: 6. Update PaymentIntent status
ItPay->>Merchant: 7. Reconciliation webhook
Merchant->>Merchant: 8. Reconcile settlement<br/>vs. ItPay records

Note over Payer,Merchant: I. ItPay initiates — generates QR, tracks state<br/>II. Channel handles — QR scan → fund transfer → settlement<br/>III. ItPay observes — receives callback, updates status<br/>IV. Merchant reconciles — compares channel settlement vs. ItPay ledger

Flow breakdown:

Payer ──→ Payment Channel (Alipay/WeChat/PromptPay) ──→ Merchant

Settlement notification


ItPay Gateway
(reconciliation only)
  1. Payment initiation — ItPay generates the PaymentIntent and QR, but the actual payment flow goes directly between the payer and the payment channel
  2. Channel settlement — The payment channel (Alipay, WeChat, etc.) handles the entire funds transfer and settles directly to the merchant's own account
  3. Callback notification — The channel sends a settlement callback to ItPay's webhook endpoint
  4. Reconciliation only — ItPay tracks the state machine (PaymentIntent status, invoice status) but never intermediates the funds

Platform Fees

Platform operators collect their fees through one of two mechanisms:

  • Channel split (preferred) — If the payment channel supports split settlement (e.g. Alipay's merchant sub-account split), the fee is deducted at the channel level before funds reach the merchant
  • Manual invoice — Where channel split is unavailable, the platform issues a separate fee invoice to the merchant, settled via the same ItPay flow

Design Rationale

  • Regulatory simplicity — No money transmitter or payment aggregator licenses required for gateway operators
  • Counterparty risk elimination — Merchant funds are never at risk from the gateway operator's insolvency
  • Audit clarity — Settlement happens at the channel level; ItPay's records are purely reconciliation data

6. KYC Levels

ItPay enforces tiered KYC (Know Your Customer) / KYB (Know Your Business) requirements. Higher verification levels unlock higher transaction limits.

LevelRequirementsDaily Volume LimitPer-Transaction LimitFeatures
0 — AnonymousNone (wallet address or phone only)$100$10QR receive only, no webhooks
1 — BasicPhone + government ID verification$1,000$100Full API access, webhooks
2 — EnhancedLevel 1 + address proof + selfie verification$10,000$1,000Higher rate limits, refund capability
3 — InstitutionalLevel 2 + business registration + compliance questionnaire$100,000+$10,000Custom limits, multi-user, reconciliation API

Verification Flow

  1. Merchant submits documents via the ItPay dashboard or API
  2. Automated document verification (OCR + liveness check)
  3. Manual review for Levels 2 and 3 (24-48 hour SLA for Level 2, 3-5 business days for Level 3)
  4. Upon approval, the merchant's kyc_level field is updated on their profile

Rate Limits by KYC Level

LevelRequests / Second
010
1100
2500
32,000+ (configurable)

7. Encryption & Data Protection

Transport Security (TLS 1.3)

All ItPay API endpoints require TLS 1.3:

  • HTTPS-only — no plain-text HTTP endpoints
  • Certificate pinning is recommended for production SDK clients
  • Perfect Forward Secrecy (PFS) enforced via ECDHE key exchange
  • Minimum cipher suite: TLS_AES_128_GCM_SHA256

mTLS for Channel Connections

For high-security connections between ItPay and payment channels (Alipay, WeChat, PromptPay), mutual TLS (mTLS) is used:

sequenceDiagram
participant ItPay as ItPay Gateway
participant Channel as Payment Channel<br/>(Alipay/WeChat)

ItPay->>Channel: ClientHello (TLS 1.3)
Channel->>ItPay: ServerHello + Certificate + CertificateRequest
ItPay->>ItPay: Verify channel's server certificate (CA chain)
ItPay->>Channel: Client Certificate (ItPay identity)
Channel->>Channel: Verify ItPay's client certificate
Channel->>ItPay: mTLS handshake complete
ItPay->>Channel: Encrypted API request (payment intent)
Channel->>ItPay: Encrypted API response
Note over ItPay,Channel: Both ends mutually authenticated
Note over ItPay,Channel: Traffic encrypted with TLS 1.3 session keys
PropertyValue
ProtocolTLS 1.3 (no fallback to 1.2)
Client authRequired — ItPay presents a client certificate signed by a trusted CA
Server authRequired — channel presents a server certificate from a trusted CA
Certificate rotationEvery 90 days via automated ACME/SPIFFE enrollment
RevocationOCSP stapling enforced on every connection

At-Rest Encryption

Sensitive data is encrypted at rest using AES-256-GCM:

Data TypeEncryptionKey Management
KYC documents (ID scans, address proofs)AES-256-GCM with per-document keyAWS KMS / GCP Cloud KMS with automatic key rotation
API key secretsbcrypt hash (not reversible)N/A — secrets are hashed, never stored in plaintext
PII fields (name, phone, address)AES-256-GCM column-level encryptionEnvelope encryption with monthly key rotation
Payment channel tokensAES-256-GCM with HSM-backed master keyHardware Security Module with annual rotation
Logs & audit trailsSigned with Ed25519 for tamper evidencePublic keys published for independent verification

PII Minimization

ItPay follows the data minimization principle:

  • Only the minimum required PII is collected for each KYC level
  • PII is never shared with payment channels (ItPay passes only the PaymentIntent ID)
  • KYC documents are auto-purged 90 days after verification completes (retained only for regulatory compliance periods where required)
  • Logs are scrubbed of PII before storage — agent_id references are used instead of real names or contact details
  • Merchant data is logically isolated per tenant using row-level security in the database

Security Headers (API Gateway)

All API responses include standard security headers:

HeaderValue
Strict-Transport-Securitymax-age=63072000; includeSubDomains; preload
Content-Security-Policydefault-src 'none'
X-Content-Type-Optionsnosniff
X-Frame-OptionsDENY
Cache-Controlno-store

Summary

LayerProtectionMechanism
API AuthenticationRequest origin verificationHMAC-SHA256 per-request signing + nonce
Anti-ReplayPrevents request reuse5-min timestamp window + nonce + Redis SETNX idempotency
QR SecurityPrevents QR fraudDynamic generation, 15-min expiry, single-use consumption, in-channel HMAC
Webhook IntegrityVerifies callback authenticityHMAC-SHA256 with constant-time comparison
Fund SafetyNo fund intermediationChannel-direct settlement, platform fees via split/invoice
IdentityMerchant trustTiered KYC Levels 0–3
Data ProtectionEncryption in transit and at restTLS 1.3, mTLS for channel connections, AES-256-GCM, PII minimization