Troubleshooting

Fix a payment webhook returning 401

Diagnose webhook 401 errors caused by raw-body mutation, wrong secrets, timestamp drift, proxy headers, or signature formatting.

A 401 from your webhook route usually means the request reached the application but authentication failed. Do not bypass verification to make the test green. Use the workbench delivery timestamp and event ID, then inspect each input to the HMAC calculation without logging the secret itself.

The highest-frequency cause is body mutation: JSON middleware parses the payload before signature verification, and the application signs a newly serialized string rather than the exact bytes received. Environment mismatch, stale timestamps, stripped headers, and encoding differences are the next checks.

Design principles

The boundaries that keep this flow reliable

01

Reproduce one event

Use a single test event and correlate its event ID across delivery and application logs. Multiple changing inputs obscure the cause.

02

Log safe diagnostics

Record body byte length, timestamp, header presence, algorithm, and computed digest prefix only in protected logs. Never log the secret.

03

Fix the boundary

Configure raw-body handling, proxy forwarding, and secrets at deployment level instead of adding insecure fallback verification.

Implementation workflow

From request to a durable result

  1. 1

    Confirm header arrival

    Verify X-VyaparGateway-Signature and X-VyaparGateway-Timestamp reach the application with no proxy allowlist removing them.

  2. 2

    Compare the active secret

    Confirm the deployed environment uses the current dashboard webhook secret and that a recent rotation reached every instance.

  3. 3

    Inspect raw-body timing

    Ensure the exact bytes are captured before JSON parsing, decompression transformations, or framework body coercion.

  4. 4

    Validate message construction

    Use timestamp + '.' + raw body, HMAC-SHA256, expected hexadecimal encoding, constant-time comparison, and an accurate server clock.

Safe diagnostic checklist in code

ts
logger.info("webhook verification input", {
  eventTrace: request.headers.get("X-Request-Id"),
  signaturePresent: Boolean(signature),
  timestampPresent: Boolean(timestamp),
  rawBodyBytes: rawBody.byteLength,
  clockSkewSeconds: Math.round(Date.now() / 1000) - Number(timestamp),
  // Never log webhookSecret or the complete payment payload.
});

Production checklist

Verify before going live

  • Signature and timestamp headers reach the application
  • All deployed instances use the same active environment secret
  • Raw bytes are captured before JSON parsing
  • Server clock is synchronized
  • Digest encoding and timing-safe comparison match the documented recipe

Failure recovery

401 cause map

Works locally, fails behind proxy

Check whether the proxy forwards custom headers and whether it decompresses or rewrites the request body before your application sees it.

Only some requests fail

One deployment instance may have an old secret, or a subset of payloads may expose a body parser difference. Compare instance ID and byte length.

Fails after secret rotation

Update every instance atomically or support a short, controlled dual-secret rotation window. Remove the previous secret promptly after confirmation.

FAQ

Questions developers ask

Should I return 200 when signature verification fails?

No. Reject unauthenticated requests. Fix the verification inputs rather than hiding the failure.

Can whitespace change the signature?

Yes. HMAC covers exact bytes, so whitespace, newline, escaping, or key-order changes produce a different digest.

What if my framework cannot provide raw body?

Configure route-specific raw-body middleware or use a lower-level request interface before the framework parses JSON.

Build the real flow

Move from guide to a verified ₹1 payment

The dashboard checklist takes you through merchant connection, payment verification, webhook testing, and go-live readiness.

Start setup