← All articles

Secrets in Webhook Configurations: How Callback URLs and Signing Keys Get Exposed

July 4, 2026

Webhooks Are Everywhere — and So Are Their Secrets

Nearly every modern SaaS integration runs on webhooks: Stripe fires one when a payment succeeds, GitHub fires one on every push, PagerDuty fires one when an alert triggers. Setting one up feels trivial — paste a URL, copy a signing key, done. That simplicity is exactly the problem.

Webhook configurations carry two distinct classes of credential, and teams routinely mishandle both:

  • The signing secret — a shared HMAC key used to verify that an inbound payload really came from the sender (e.g., whsec_… from Stripe, WEBHOOK_SECRET from GitHub).
  • The callback URL itself — which often embeds authentication tokens, API keys, or session identifiers in the query string (e.g., https://api.example.com/hooks?token=sk_live_…).

Lose either one and an attacker can forge events, replay transactions, or call your internal endpoints directly.

How Webhook Secrets Actually Leak

1. Hardcoded in application config

The quickest way to wire up a webhook handler is to paste the signing secret directly into the code that verifies it. A common pattern in Node.js:

// ❌ Do not do this
const webhookSecret = "whsec_4f9a2b1c...";
const event = stripe.webhooks.constructEvent(payload, sig, webhookSecret);

This ends up committed to the repository, included in every future fork, and exposed to everyone with read access — which, for a public repo, means everyone on the internet.

2. Logged by middleware or request inspection tools

Signature verification requires reading the raw request body and the Stripe-Signature or X-Hub-Signature-256 header. Many teams add a temporary console.log(req.headers) while debugging — and never remove it. That header contains the computed HMAC, not the raw secret, but the URL-embedded token pattern is far more dangerous: logging req.url captures the token verbatim and ships it to your log aggregator.

3. Pasted into ticketing and chat tools

A developer debugging a failed delivery copies the full webhook URL — including the embedded ?token= — into a Jira comment or a Slack thread. That credential now lives in a collaboration tool with its own retention policy, access controls, and export history.

4. Stored in plaintext in the dashboard or IaC

Webhook endpoints registered via Terraform, Pulumi, or CloudFormation often store the callback URL and any associated secret as a resource attribute. Terraform state is JSON — and Terraform state is frequently committed, uploaded to unprotected S3 buckets, or shared in CI logs.

5. Exposed through the provider's own UI

Several SaaS platforms display the full signing secret in the dashboard indefinitely. If a dashboard session is compromised — via a stolen session cookie, a misconfigured OAuth app, or a phishing attack — the attacker can harvest every webhook secret the account has ever issued.

Why a Leaked Webhook Signing Secret Is Serious

It's tempting to think of a signing secret as a low-value credential: it doesn't grant API access directly. That framing is wrong. A leaked signing secret lets an attacker:

  • Forge arbitrary events. They can craft a payload claiming a payment succeeded, a user was verified, or an admin action was approved — and your application will accept it as legitimate.
  • Replay captured events. If you don't enforce event timestamp windows, a replayed legitimate event can trigger duplicate order fulfillment, account credits, or permission grants.
  • Map your internal infrastructure. The callback URL itself reveals your internal hostname, routing logic, and sometimes authentication scheme.

How to Audit Your Webhook Configuration Right Now

  1. Search your repos for common signing-key prefixes. Run a grep across your full history, not just the current working tree:
    git log --all -p | grep -E "(whsec_|WEBHOOK_SECRET|hook.*token)" 
    This catches keys that were removed in a later commit but are still reachable in history.
  2. Audit callback URLs registered with providers. Log in to each SaaS dashboard (Stripe, GitHub, Twilio, SendGrid, etc.) and inspect the registered endpoint URLs. Any URL containing a query-string parameter like token, key, auth, or secret should be rotated to use header-based authentication instead.
  3. Review your Terraform and Pulumi state files. Search state JSON for the string patterns used by your webhook providers. If you find secrets in state, rotate immediately and move the values to a secrets manager referenced by data source, not hardcoded in the resource block.
  4. Check your log aggregator for header dumps. Query your log platform (Datadog, CloudWatch, Splunk) for x-stripe-signature, x-hub-signature, or any URL containing token-like query parameters. If you find them, scrub the logs and add a log redaction rule.
  5. Rotate any secret you can't fully account for. If you can't confirm exactly where a signing secret has been stored and that every location is secure, treat it as compromised. Most providers let you issue a new secret without downtime by accepting both the old and new secret during a transition window.

Secure Webhook Patterns to Adopt

Store signing secrets in a secrets manager, not environment variables

Read the secret at runtime from AWS Secrets Manager, HashiCorp Vault, or GCP Secret Manager. Never inject it as a plaintext environment variable that can be read by any process in the container or printed by a misconfigured health-check endpoint.

Authenticate via headers, not query strings

If you control the callback URL design, use a custom header for authentication (X-Webhook-Token: …) rather than a query string parameter. Headers are less likely to be logged verbatim by load balancers, CDNs, and proxies.

Enforce timestamp validation

Most webhook providers include a timestamp in the signed payload. Reject any event whose timestamp is more than five minutes old. This eliminates replay attacks even if the signing secret is briefly exposed.

Scope the handler endpoint

Your webhook handler should do exactly one thing: validate the signature, parse the event type, and enqueue the event for asynchronous processing. It should never perform privileged operations inline. This limits the blast radius of a forged or replayed event.

Rotate secrets on a schedule, not just after incidents

Treat webhook signing secrets like any other long-lived credential: rotate them quarterly, document the rotation procedure, and test it in staging before you need to do it under pressure in production.

Fit This Into Your Broader Secret Scanning Practice

Webhook secrets are easy to miss in a standard secret scan because their formats vary by provider and they often appear in configuration files rather than source code. A thorough scan needs to cover your entire repo history, your IaC state, your CI artifact output, and your environment configuration — not just the files currently checked in to main.

If you want a fast baseline across your repositories, run a free GhostCred scan — it checks for exposed signing keys, embedded tokens, and IAM misconfigurations across your codebase in about 60 seconds and maps findings to SOC 2 and HIPAA controls so you have something concrete to hand an auditor.

Key Takeaways

  • Webhook signing secrets and URL-embedded tokens are real credentials with real exploit paths — treat them accordingly.
  • They leak through committed config, debug logging, chat tools, and IaC state — all channels that need to be part of your scanning surface.
  • Mitigations are straightforward: secrets manager storage, header-based auth, timestamp validation, and regular rotation.
  • Audit your provider dashboards and your full git history — not just HEAD.

See what's exposed in your own code.

Run a free scan