Leypal Corp Docs
Webhooks

Errors & Troubleshooting

Complete reference for webhook HTTP response codes, retry behavior, signature verification errors, and a step-by-step troubleshooting flowchart.

Webhook Errors & Troubleshooting

Webhook delivery is asynchronous. Your endpoint must be publicly accessible, respond within the timeout window, and return the correct HTTP status codes. When something goes wrong — network errors, signature mismatches, or endpoint crashes — Leypal retries delivery automatically using exponential backoff.

This guide covers three categories of issues you may encounter:

  1. HTTP response codes — what each code means and how Leypal reacts
  2. Signature verification errors — common causes and fixes for authentication failures
  3. Delivery issues — reachability, timeouts, and permanent failures

Use the troubleshooting flowchart to diagnose unknown failures, and the production checklist before going live.


What Leypal Sends

Every webhook is an HTTPS POST request with the following properties:

PropertyValue
MethodPOST
Content-Typeapplication/json
X-Leypal-SignatureHMAC-SHA256 signature of the raw request body
BodyJSON payload (see Webhook Events for full structure)
Timeout30 seconds — Leypal closes the connection after 30 seconds with no response

Important: Leypal signs the raw request body bytes, not a serialized JSON string. Your endpoint must read the raw body before parsing to verify the signature correctly.

For signature verification implementation details, see Webhook Signature Verification.


HTTP Response Codes

Leypal treats any 2xx response as a successful delivery. Any non-2xx response (including redirects and timeouts) triggers the retry policy.

2xx — Success (No Retry)

CodeStatusBehaviorYour Action
200OKWebhook delivered successfullyDefault response — return this when you receive and acknowledge the webhook
201CreatedWebhook processed; resource createdAcceptable if your handler creates a resource as part of processing
202AcceptedWebhook queued for background processingIdeal response — signals you received the payload and will process it asynchronously
204No ContentWebhook received; no response bodyAcceptable if you prefer not to send a response body

Best practice: Return 200 or 202 immediately, before running any business logic. Processing the webhook in the request handler (e.g., querying the database, sending emails) adds latency and risks exceeding the 30-second timeout. Instead, enqueue the event and process it in a background job.

3xx — Redirects (Fail + Retry)

CodeStatusBehaviorYour Action
301Moved PermanentlyWebhook marked as failed; Leypal does NOT follow redirectsUpdate webhook URL in dashboard to the final destination
302FoundSame as 301Update webhook URL to avoid the redirect
307Temporary RedirectSame as 301Verify endpoint URL is the canonical URL

Redirects indicate a misconfiguration. Leypal will retry up to 5 times, but each attempt hits the same redirect. Update the webhook URL in your API Key dashboard to point directly to the final endpoint.

4xx — Client Errors (Fail + Retry)

CodeStatusBehaviorYour Action
400Bad RequestEndpoint rejected request formatCheck your signature verification logic; verify middleware ordering
401UnauthorizedSignature verification failed or missing headerSee Signature Verification Errors
403ForbiddenEndpoint exists but rejected requestCheck your authorization logic; verify the request is from Leypal
404Not FoundWebhook path doesn't existVerify endpoint URL in dashboard; check server routing
408Request TimeoutEndpoint took >30 seconds to respondReturn 202 immediately; move processing to background job
429Too Many RequestsRate limit exceeded on your endpointImplement rate limit exemption for Leypal's IP range, or raise your limits

All 4xx responses are retried up to 5 times. Leypal assumes the error may be transient (e.g., a brief overload or race condition). Fix the root cause — the retries will eventually succeed or exhaust attempts.

401 and 403 are the most common codes during initial setup. If you see them, go directly to Signature Verification Errors.

5xx — Server Errors (Fail + Retry)

CodeStatusBehaviorYour Action
500Internal Server ErrorEndpoint threw an unhandled exceptionCheck server logs; fix the error and redeploy
502Bad GatewayReverse proxy couldn't reach your appVerify app server is running; check nginx/caddy/ALB config
503Service UnavailableEndpoint overloaded or in maintenance modeScale up or reduce processing time per request
504Gateway TimeoutProxy gave up waiting for your appReturn 202 immediately; reduce handler execution time

All 5xx responses are retried up to 5 times. These are temporary by definition — the assumption is your server will recover. Check your application logs for stack traces.

Network Errors (Fail + Retry)

Error TypeBehaviorYour Action
Connection refusedRetried up to 5 timesVerify endpoint is running and listening on the correct port
DNS resolution failureRetried up to 5 timesVerify domain DNS is configured; check with dig yourendpoint.com
Connection timeoutRetried up to 5 timesVerify firewall allows inbound HTTPS (port 443)
TLS/SSL errorRetried up to 5 timesEnsure SSL certificate is valid; check certificate chain

Local development note: Leypal cannot reach localhost or 127.0.0.1. Use ngrok or localtunnel to create a public tunnel to your local server.


Retry Policy

When a webhook delivery fails, Leypal retries automatically with exponential backoff:

AttemptBackoffCumulative Time After First Failure
1Initial delivery (no backoff)0 min
2~5 minutes~5 min
3~10 minutes~15 min
4~20 minutes~35 min
5~40 minutes~75 min

After the 5th failed attempt, the webhook is marked permanently failed. No further retries are made. To receive the event again, you must trigger the action that caused it (e.g., create a new signature request).

A webhook is marked successful when:

  • Leypal receives a 2xx response from your endpoint before the 30-second timeout

A webhook is marked failed when:

  • All 5 attempts are exhausted without a 2xx response
  • Every attempt results in a network error (unreachable endpoint)

Idempotency note: Because Leypal retries on failure, your endpoint may receive the same event more than once. Always implement idempotent processing — use the webhookId field to detect and skip duplicate deliveries.


Signature Verification Errors

These errors occur when your endpoint returns 401 Unauthorized or when your signature check fails silently.

ErrorCauseSymptomFix
Invalid signatureComputed HMAC doesn't match X-Leypal-Signature headerEndpoint returns 401; logs show signature mismatchSee fixes below
Missing X-Leypal-Signature headerWebhook not registered or secret not generatedHeader absent in req.headersVerify webhook URL and secret in API Key dashboard
Endpoint not reachableLeypal cannot connect to your URLWebhook appears as failed in history; responseCode is nullCheck firewall, DNS, endpoint availability
Invalid algorithmUsing SHA1, MD5, or other algorithm instead of SHA256Signature hex string format mismatchAlways use sha256 with Node.js crypto.createHmac

Fixing "Invalid Signature" (401)

The most common cause is a mismatch between how you compute the HMAC and how Leypal computes it. Check these in order:

1. Wrong or outdated webhook secret

The secret in your .env must exactly match the one in your Leypal API Key dashboard. Even a single extra space or character will cause every webhook to fail.

# Print your current secret (no trailing newline)
echo -n "$WEBHOOK_SECRET" | wc -c
# Compare against the character count shown in your dashboard

If you recently rotated the secret in the dashboard, update your .env and redeploy immediately.

2. Not reading the raw body

The HMAC is computed over the raw request bytes — before JSON parsing. If you let Express parse the body first, the raw bytes are gone and you cannot recompute the correct signature.

// WRONG — JSON parsed body; raw bytes lost
app.use(express.json());
app.post('/webhooks', (req, res) => {
  // req.body is already an object; can't get raw bytes back
  const sig = computeHmac(webhookSecret, JSON.stringify(req.body)); // FAILS
});

// CORRECT — raw body preserved for webhook route
app.post('/webhooks', express.raw({ type: 'application/json' }), (req, res) => {
  // req.body is a Buffer containing the raw bytes
  const sig = crypto.createHmac('sha256', webhookSecret)
    .update(req.body) // Buffer, not string
    .digest('hex');
  // now compare with X-Leypal-Signature header
});

3. Using string equality instead of timing-safe comparison

// WRONG — timing attack possible
if (computedSig === req.headers['x-leypal-signature']) { ... }

// CORRECT — timing-safe
const a = Buffer.from(computedSig);
const b = Buffer.from(req.headers['x-leypal-signature']);
if (a.length !== b.length || !crypto.timingSafeEqual(a, b)) {
  return res.status(401).send('Invalid signature');
}

For a complete implementation, see Webhook Signature Verification.

Fixing "Missing X-Leypal-Signature Header"

If the header is absent on every request, the webhook may not be registered:

  1. Go to your API Key dashboard → Webhooks section
  2. Verify your endpoint URL is listed and Active
  3. Verify a webhook secret is generated (click the secret field — it should show sk_webhook_xxx)
  4. If the secret was deleted or never created, click Generate Secret and copy the new value to your .env

Webhook Delivery History API

When a webhook is not arriving, the delivery history is the first place to look. It shows exactly what Leypal sent, what your endpoint returned, and why delivery failed.

Endpoint: GET /api/v1/webhook-notifications

Authentication: API key as Bearer token

curl -X GET https://api.leypal.dev/api/v1/webhook-notifications \
  -H "Authorization: Bearer sk_live_xxx" \
  -H "Content-Type: application/json"

Optional query parameters:

ParameterTypeDescription
eventstringFilter by event type (e.g., signature.created)
statusstringFilter by delivery status: success, failed, pending
limitnumberResults per page (default: 50, max: 100)
offsetnumberPagination offset

Response fields per delivery record:

FieldTypeDescription
idstringNotification record ID
eventstringEvent type (e.g., signature.completed)
webhookIdstringUnique webhook delivery ID (matches X-Leypal-Webhook-Id header)
organizationIdstringYour organization ID
statusstringsuccess | failed | pending
requestBodyobjectExact JSON payload Leypal sent
responseCodenumberHTTP status code your endpoint returned
responseBodystringResponse body your endpoint sent
responseHeadersobjectResponse headers your endpoint sent
deliveredAtstringISO 8601 timestamp of delivery attempt
nextRetryAtstringISO 8601 timestamp of next retry (if pending/failed)
failureReasonstringWhy the webhook failed (e.g., "connection timeout")
attemptsnumberTotal delivery attempts made so far

When to use this: If a webhook never arrives, query this endpoint to see whether Leypal attempted delivery at all. If no record exists, the triggering event did not fire. If a record exists, responseCode and failureReason tell you exactly what went wrong.


Troubleshooting Flowchart

Use this flowchart to diagnose webhook delivery problems systematically:

My webhook is not being delivered

├─ Step 1: Is the webhook URL registered in your API Key dashboard?
│  ├─ No → Go to API Key dashboard → Webhooks → Add URL → Generate Secret
│  └─ Yes → Continue to Step 2

├─ Step 2: Is your endpoint publicly accessible from the internet?
│  ├─ No (localhost/private) → Use ngrok or localtunnel for testing
│  │                           Deploy to a public server for production
│  └─ Yes → Continue to Step 3

├─ Step 3: Did Leypal attempt delivery?
│  │  Check: GET /api/v1/webhook-notifications?status=failed
│  ├─ No records found → Triggering event did not fire
│  │                     Verify you performed the action (e.g., created signature)
│  └─ Records exist → Continue to Step 4

├─ Step 4: What does the delivery record show?
│  ├─ responseCode: 2xx → Webhook was delivered; check your own application logs
│  ├─ responseCode: 401 → Signature verification failing
│  │                       See: Signature Verification Errors section
│  ├─ responseCode: 4xx → Endpoint rejected request
│  │                       See: HTTP Response Codes section
│  ├─ responseCode: 5xx → Endpoint crashed or returned an error
│  │                       Check: Your server logs for stack traces
│  └─ responseCode: null → Network error; Leypal could not connect
│                           Continue to Step 5

├─ Step 5: Can Leypal reach your endpoint?
│  │  Test: curl -X POST https://yourendpoint.com/webhooks -d '{}' -v
│  ├─ curl fails → Fix: firewall rules, DNS, SSL certificate, server running?
│  └─ curl succeeds → Webhook handler is crashing on startup or early in the request
│                      Check: Application logs for errors before response is sent

├─ Step 6: Is signature verification the problem?
│  │  Check: Is responseCode 401 in the history?
│  ├─ Yes → See: Signature Verification Errors section above
│  └─ No → Check application logs for errors inside the webhook handler

└─ Still stuck?
   ├─ Enable verbose logging in your webhook handler
   ├─ Check webhook history for exact requestBody and responseBody
   ├─ Verify your endpoint with: curl -X POST <url> -H "Content-Type: application/json" -d '{"test":true}'
   └─ Contact Leypal support with:
      - Event type that is not delivering
      - Your Organization ID
      - Webhook ID (from history API)
      - Timestamp of expected delivery
      - Server logs showing what your endpoint received

Common Scenarios & Solutions

ScenarioWhat You SeeRoot CauseSolution
Endpoint returns 200 but webhook shows as failedHistory: status: failed, responseCode: 200Endpoint took >30 seconds to respond before returning 200Return 202 in under 1 second; move all business logic to a background queue
"Invalid signature" even though secret is correctEvery webhook returns 401 UnauthorizedRaw body not preserved; or string equality usedAdd express.raw() before JSON middleware; use crypto.timingSafeEqual()
Webhook worked yesterday, stopped todayNo webhooks delivered since a specific timeEndpoint went down, or webhook secret was rotatedCheck if endpoint is running; compare secret in .env vs dashboard
Test webhook succeeds but real webhooks failManual test: delivered; real event: failedDifferent endpoint URL (dev vs prod) or firewall differenceVerify production webhook URL is registered; check production firewall
Getting 404 on webhook deliveryHistory: responseCode: 404Webhook path doesn't exist on serverVerify URL path in dashboard; check server routing config
Leypal keeps showing pending retries after fixHistory: status: pending even after endpoint is healthyPending retries are on a schedule — they will execute at nextRetryAtWait for the next retry; or trigger a new event to generate a fresh webhook
Webhook delivered but event type not handledApplication silently ignores eventSwitch/if block doesn't cover the event typeLog all incoming event types; add a handler for the missing event
Same webhook delivered twiceDuplicate entries in your databaseRetry succeeded; or network delivered twiceImplement idempotency: check webhookId before processing, skip duplicates

Production Checklist

Before deploying your webhook handler to production, verify each item:

  • Endpoint uses HTTPS (not HTTP)
  • Production webhook URL is updated in Leypal API Key dashboard (not dev/staging URL)
  • Webhook secret is stored in environment variable or secrets manager (not hardcoded)
  • .env file is NOT committed to git (add to .gitignore)
  • Using crypto.timingSafeEqual() for signature comparison (not ===)
  • Using express.raw({ type: 'application/json' }) middleware before JSON parsing
  • Endpoint returns 200 or 202 in under 1 second
  • Business logic is processed asynchronously (queue system or background job)
  • webhookId is logged for every incoming event (enables debugging)
  • Webhook delivery history is monitored periodically (GET /api/v1/webhook-notifications)
  • Duplicate delivery is handled via idempotency check on webhookId
  • Error handling is in place — unhandled exceptions return 500 cleanly, not crash the process

Getting Help

If you have worked through the troubleshooting flowchart and the problem persists, gather the following before contacting support:

  • Event type that is not delivering (e.g., signature.completed)
  • Organization ID (visible in your dashboard)
  • Webhook ID from the delivery history API (webhookId field)
  • Timestamp of the expected delivery
  • Server logs from the time of the delivery attempt
  • Webhook history entry showing requestBody, responseCode, and failureReason
GuideWhat It Covers
Webhook SetupRegistering endpoints, generating secrets, managing webhook configuration
Webhook Signature VerificationComplete HMAC-SHA256 implementation with code examples
Webhook EventsFull payload structure for all event types

On this page