Skip to main content

Integrity and Source Verification

Every payload from a payment processor carries two risks: tampering (someone modified the data in transit) and impersonation (someone forged the sender's identity). The Prism provides the tools to eliminate both risks:

Verification TypeWhat It ChecksAttack Prevented
IntegrityAmount, currency, and transaction ID match your recordsData tampering
SourceCryptographic signature using shared secretsImpersonation, forged webhooks

Before trusting any webhook or redirect response, the Prism helps you verifying both.

Webhook Signature Verification

Typically Payment processors sign webhooks with a shared secret. You verify the signature before trusting the payload. Prism handles this for every supported processor.

// Incoming webhook from Stripe
const payload = req.body;
const signature = req.headers['stripe-signature'];

// Prism verifies the signature
const result = await client.events.handle({
payload: payload,
signature: signature,
connector: 'stripe',
webhookSecrets: {
secret: process.env.STRIPE_WEBHOOK_SECRET
}
});

// result.source_verified tells you if the webhook is authentic
console.log(result.sourceVerified); // true or false

Supported Algorithms:

AlgorithmProcessors Using ItSecurity Level
HMAC-SHA256Stripe, Adyen, Checkout.comRecommended
HMAC-SHA1Legacy systemsAcceptable
HMAC-SHA512High-security connectorsMaximum

The Verification Flow

// 1. Receive webhook
app.post('/webhooks/stripe', async (req, res) => {
const payload = req.body;
const signature = req.headers['stripe-signature'];

// 2. Verify before processing
const result = await client.events.handle({
payload: payload,
signature: signature,
connector: 'stripe',
webhookSecrets: { secret: process.env.STRIPE_WEBHOOK_SECRET }
});

// 3. Check verification result
if (!result.sourceVerified) {
// Reject forged webhooks immediately
return res.status(401).json({ error: 'Invalid signature' });
}

// 4. Process verified event
await processPaymentEvent(result.eventResponse);
res.json({ received: true });
});

Never process unverified webhooks. A forged webhook could mark a failed payment as successful, causing you to ship product for unpaid orders.

Amount and Currency Verification

Webhooks include amounts. Prism verifies these match your records.

{
"event_type": "PAYMENT_INTENT_SUCCESS",
"amount": {
"minor_amount": 5999,
"currency": "USD"
},
"expected_amount": {
"minor_amount": 5999,
"currency": "USD"
}
}

If amounts mismatch, Prism flags the discrepancy:

{
"error": {
"code": "AMOUNT_MISMATCH",
"message": "Webhook amount does not match expected amount",
"webhook_amount": 4999,
"expected_amount": 5999,
"currency": "USD"
}
}

This prevents attacks where an attacker modifies the webhook payload to show a lower amount than actually charged.

Transaction ID Verification

Prism tracks transaction IDs across the lifecycle. When a webhook arrives, it verifies the ID matches an in-flight transaction.

CheckFailure Mode
ID exists in systemReject unknown transaction IDs
Status transition validReject invalid state changes (e.g., SUCCEEDED → PENDING)
Amount matchesReject amount tampering
Currency matchesReject currency switching attacks

Error: Signature Verification Failed

{
"error": {
"code": "SIGNATURE_VERIFICATION_FAILED",
"message": "Webhook signature does not match payload",
"connector": "stripe",
"suggestion": "Check your webhook secret is correct and the payload was not modified"
}
}

Common causes:

  • Wrong webhook secret configured
  • Payload modified in transit (proxy, middleware)
  • Signature header missing or malformed

Error: Replay Attack Detected

{
"error": {
"code": "DUPLICATE_EVENT",
"message": "Event ID already processed",
"event_id": "evt_1234567890",
"processed_at": "2026-03-19T10:30:00Z"
}
}

Prism tracks event IDs to prevent replay attacks. An attacker cannot resend an old webhook to trigger duplicate actions.

Webhook Secrets Configuration

Configure secrets per connector:

const client = new ConnectorServiceClient({
connector: 'stripe',
webhookSecrets: {
secret: process.env.STRIPE_WEBHOOK_SECRET,
// Some connectors use additional secrets
additionalSecret: process.env.STRIPE_ADDITIONAL_SECRET
}
});

Secret Rotation:

  • Generate new secret in processor dashboard
  • Update Prism configuration
  • Old secret continues working during transition
  • Remove old secret after 24 hours

Testing Webhook Verification

Test with forged signatures to verify your rejection logic:

# Send webhook with invalid signature
curl -X POST http://localhost:8080/webhooks/stripe \
-H "Content-Type: application/json" \
-H "Stripe-Signature: forged_signature" \
-d '{"type":"payment_intent.succeeded","id":"evt_test"}'

# Expected: 401 Unauthorized

Best Practices

  1. Verify before processing — Never trust webhooks without signature verification
  2. Use HTTPS — Webhooks over HTTP expose secrets to interception
  3. Log verification failures — Repeated failures indicate attack attempts
  4. Implement idempotency — Handle duplicate webhooks gracefully even with verification
  5. Rotate secrets quarterly — Limit exposure window if a secret leaks

Next Steps