Back to blog

What is a webhook and how does it work? Complete guide for developers

If you work with system integrations, you’ve probably heard of webhooks. They are the backbone of communication between modern applications — from Stripe to GitHub, from Mercado Pago to Slack.

In this guide, we’ll explain what a webhook is, how it works in practice, when to use it, and how to test it.

What is a webhook?

A webhook is an automatic HTTP notification sent from one system to another when an event occurs. Instead of repeatedly asking “has anything new happened?” (polling), the service notifies you automatically.

In practice, a webhook is an HTTP POST sent to a URL you configure. When the event occurs (e.g., payment confirmed), the service sends a JSON to your URL with the event data.

Real-world example

Imagine you integrate with Stripe to receive payments. When a customer pays:

  1. Stripe detects the payment_intent.succeeded event
  2. Stripe makes a POST to the URL you registered
  3. Your server receives the JSON with the payment data
  4. You process it (update the order, send an email, etc.)
{
  "type": "payment_intent.succeeded",
  "data": {
    "object": {
      "id": "pi_3ABC123",
      "amount": 4900,
      "currency": "brl",
      "status": "succeeded"
    }
  }
}

Webhook vs API Polling: what’s the difference?

FeatureWebhookAPI Polling
Who initiatesThe external service (push)Your system (pull)
LatencyNear zero — instant notificationDepends on polling interval
Resource usageLow — only when there’s an eventHigh — constant requests
ComplexityRequires a public endpointSimpler to implement
ScalabilityExcellentProblematic at high volume

Summary: webhooks are more efficient, faster, and more scalable. Use polling only when the service doesn’t offer webhooks.

How does a webhook work in practice?

The webhook flow has 4 stages:

1. URL registration

You configure in the external service (e.g., Stripe, GitHub) the URL that will receive notifications. This URL needs to be public and accessible via HTTPS.

2. Event occurs

A user makes a payment, a commit is pushed, an order is created — any event that the service monitors.

3. Webhook delivery

The service makes an HTTP request (usually POST) to the registered URL, with a JSON body containing the event data.

4. Processing

Your server receives the request, validates the signature (if any), processes the data, and returns a 200 OK status.

Important: if your server doesn’t return 200, most services perform automatic retries — usually with exponential backoff.

Which services use webhooks?

Virtually all modern API services:

  • Payments: Stripe, PagSeguro, Mercado Pago, PayPal, Asaas
  • Code: GitHub, GitLab, Bitbucket
  • Communication: Twilio, SendGrid, Slack
  • E-commerce: Shopify, WooCommerce, Hotmart
  • Automation: Zapier, n8n, Make

Common challenges when working with webhooks

1. Public URL during development

In local development, your localhost is not accessible from the internet. Traditional solutions like ngrok create temporary tunnels, but URLs expire and need to be reconfigured.

With HookScope, you get a permanent URL that never expires. Besides the web dashboard, you can use the HookScope CLI to receive webhooks directly on localhost:

dotnet tool install -g HookScope.Cli
hookscope login
hookscope listen my-endpoint --to http://localhost:3000

No tunnels, no URLs that change every session.

2. Debugging the payload

When the webhook arrives and something goes wrong, you need to see exactly what was sent — headers, body, query strings. Tools like HookScope show everything in real time via WebSocket.

3. Testing again (replay)

The service sent the webhook, but your code had a bug. Now you need the webhook to be sent again. With replay, you re-send any captured webhook with one click.

4. Ensuring idempotency

Webhooks can be sent more than once (retries). Your code needs to be idempotent — process the same event multiple times without side effects.

// Idempotency example
async function handleWebhook(event) {
  // Check if this event was already processed
  const existing = await db.events.findOne({ eventId: event.id });
  if (existing) return; // Already processed, skip

  // Process the event
  await processPayment(event.data);

  // Mark as processed
  await db.events.insert({ eventId: event.id, processedAt: new Date() });
}

How to test webhooks?

There are several approaches to testing webhooks during development:

Use a tool like HookScope to capture webhooks with a permanent URL. You can:

  • Inspect headers, body, and metadata in real time
  • Replay to re-test without re-triggering the event
  • Set up automatic forwarding to your localhost
  • Use the HookScope CLI to receive webhooks locally via hookscope listen

2. CLI tools (ngrok, localtunnel)

Tools like ngrok create a tunnel between the internet and your localhost. They work well, but:

  • Temporary URLs that expire
  • Need to reinstall and reconfigure
  • No history of previous requests
  • No webhook replay

Alternative: the HookScope CLI also forwards webhooks to localhost, but without tunnels — the public endpoint URL never changes and you still have access to history and replay via the dashboard.

3. Manual mock

You can simulate webhooks manually with curl:

curl -X POST http://localhost:3000/webhook \
  -H "Content-Type: application/json" \
  -d '{"type": "payment.confirmed", "amount": 4900}'

The problem is that manual mocks don’t exactly reproduce the real payload from the service.

Best practices for webhooks

  1. Always validate the signature — services like Stripe send a signature header (Stripe-Signature). Verify before processing.
  2. Return 200 quickly — process in the background if necessary. The service expects a fast response.
  3. Be idempotent — be prepared to receive the same webhook more than once.
  4. Log everything — record every received webhook for future debugging.
  5. Use HTTPS — never receive webhooks on URLs without TLS.

Conclusion

Webhooks are fundamental for modern integrations. Understanding how they work and having the right tools to test them makes all the difference in developer productivity.

If you’re starting to work with webhooks or want a more efficient way to debug integrations, create a free HookScope account and start capturing webhooks in seconds.