| name | webhook-signature-verification |
| description | Accept inbound webhooks (Stripe, GitHub, Slack, partner) and verify HMAC signatures in Apex REST. NOT for outbound webhooks. |
| category | integration |
| salesforce-version | Spring '25+ |
| well-architected-pillars | ["Security","Reliability"] |
| triggers | ["webhook hmac verification apex","stripe webhook salesforce","verify signature inbound callout","hmac sha256 apex"] |
| tags | ["webhook","hmac","signature","apex-rest"] |
| inputs | ["webhook provider HMAC spec","shared secret location"] |
| outputs | ["@RestResource endpoint with signature verification"] |
| dependencies | [] |
| version | 1.0.0 |
| author | Pranav Nagrecha |
| updated | "2026-04-28T00:00:00.000Z" |
Webhook Signature Verification
Inbound webhooks are unauthenticated by default — anyone with your endpoint URL can POST. HMAC signature verification with a shared secret is the industry-standard defense. This skill implements Stripe-style HMAC SHA-256 verification with timing-safe comparison in Apex and uses Protected Custom Metadata for the secret.
Adoption Signals
Any inbound webhook from an external SaaS (Stripe, GitHub, Twilio, Slack, Zapier).
Recommended Workflow
- Store the shared secret in Protected Custom Metadata (
Webhook_Secret__mdt.Value__c) or a Named Credential with a dummy endpoint.
- @RestResource endpoint reads
Request.headers.get('X-Provider-Signature').
- Compute HMAC SHA-256 using
Crypto.generateMac('HmacSHA256', body, secret); hex-encode.
- Timing-safe compare (constant-time loop) — equality op in Apex is not documented as timing-safe.
- Reject with 401 on mismatch; never log the body on reject (avoid replay disclosure).
Key Considerations
- Always compare hashes constant-time to prevent timing attacks.
- Include a timestamp in the signed payload and reject if older than 5 minutes (replay protection).
- Use Protected Custom Metadata for the secret — not a Custom Setting, which is readable by any user with read.
- Webhook endpoints should be public sites (no authentication) — that's normal; the signature is the auth.
Worked Examples (see references/examples.md)
- Stripe webhook — Payment success
- Replay protection — Attacker replays yesterday's valid event
Common Gotchas (see references/gotchas.md)
- Non-constant-time compare — Timing attack leaks signature.
- Secret in plain CMDT — Any reader sees it.
- Missing replay defense — Replayed events processed twice.
Top LLM Anti-Patterns (full list in references/llm-anti-patterns.md)
- No signature check ('trust by URL obscurity')
- Secret in Custom Setting
- Logging raw body on failure
Official Sources Used