ScreenshotNeo

BlogComparisons

Webhooks vs. APIs Explained With a Real-World Example

APIs pull data when your app asks. Webhooks push event notifications when something happens. Learn how Stripe and GitHub use both.

By the ScreenshotNeo team1 October 20269 min read

APIs are client-initiated requests; webhooks are provider-initiated event notifications. Your application calls an API when it wants to read or change something. A webhook calls your application when a subscribed event occurs.

A useful analogy is a delivery status. Polling an API means repeatedly asking, “Has the package arrived yet?” A webhook means the delivery service calls you when the status changes.

Both patterns normally use HTTP. A production integration often uses both: the API performs commands and retrieves current state, while webhooks announce changes so your system does not need to poll continuously.

API vs. webhook at a glance

Question API Webhook
Who starts the request? Your client application The provider when an event occurs
Communication pattern Pull: request and response Push: event delivery
When does data arrive? When you request it or run a polling job Near real time after a subscribed event
Typical purpose Read or change a resource Notify your system that something changed
What must you operate? An HTTP client, credentials, retries, and rate-limit handling A reachable endpoint, authentication, validation, retries, and idempotent processing
How do you recover? Request the current state again Reconcile with the API if a delivery is missed or delayed

GitHub describes webhooks as subscriptions that deliver data to your server when events happen, and notes that they reduce polling effort and resources. Its guidance recommends API calls when information is needed only once or intermittently. GitHub webhook documentation

Twilio SendGrid summarizes the distinction as “APIs pull, webhooks push.” Twilio SendGrid’s explanation

Real-world example: a Stripe payment

Suppose an online store needs to charge a customer.

  1. The store calls Stripe’s API to create or manage the payment operation.
  2. Stripe processes the payment asynchronously and records an event such as a successful payment or a failure.
  3. Stripe sends a webhook request to the store’s configured endpoint.
  4. The endpoint verifies the webhook signature, accepts the event, and updates the order.
  5. If the store needs authoritative details, it retrieves the payment from Stripe’s API.

This division matters because the initial API response is not always the final business state. The webhook tells your application that a provider-side event occurred; the API remains the place to retrieve current resource data. Stripe documents webhook endpoints for account and connected-account events and requires signature verification with its constructEvent() pattern. Stripe webhooks documentation

Stripe-style flow

Browser or app
    |
    | 1. POST /create-payment (your server)
    v
Your server --------------------> Stripe API
    |                                  |
    |                                  | payment event occurs
    |                                  v
    <----------- 2. POST /stripe/webhook
    |
    | 3. Verify signature
    | 4. Mark order paid
    |
    | 5. GET payment from Stripe API when details are needed

Another example: GitHub push to build

A deployment service can subscribe to a repository’s push webhook. When a push occurs, GitHub sends the event and the service starts a build. If the service later needs the current commit, issue, or repository details, it calls the GitHub REST API on demand.

This avoids repeatedly asking GitHub whether a new push exists. The webhook starts the work; the API supplies additional or authoritative data.

How to choose between an API and a webhook

Use an API when your application decides the timing

  • You need to fetch a resource once or occasionally.
  • A user clicked a button that should create, update, or delete data.
  • You need the latest state during reconciliation.
  • The provider does not offer the event you need as a webhook.
  • You are running a scheduled report and can tolerate scheduled requests.

Use a webhook when the provider knows the timing

  • You need to react soon after an event occurs.
  • Polling would create many unnecessary requests.
  • You monitor many resources and want the provider to notify you only about changes.
  • You can expose a secure, reachable HTTP endpoint.

Use both for a robust integration

A common design is command through the API, notification through a webhook, and state recovery through the API. Treat the webhook as a trigger to process or reconcile, rather than as the only copy of your business data.

Building a reliable webhook receiver

1. Expose an HTTPS endpoint

Use a stable public URL such as https://example.com/webhooks/provider. Keep the endpoint separate from browser routes so request parsing, authentication, and observability are predictable.

2. Verify authenticity before trusting the payload

Use the provider’s documented signing secret and verification method. For Stripe, verify the raw request body and the Stripe-Signature header with constructEvent(). Do not accept an event merely because it contains an expected JSON field.

3. Make processing idempotent

Providers may retry deliveries, and your own worker may retry after a timeout. Store a provider event ID with a unique constraint. If that ID has already been processed, return success without applying the business action twice.

4. Acknowledge quickly

Validate the request, persist the event, and return a success response quickly. Perform slow work in a queue or background worker. A timeout can cause the provider to retry even when your application eventually completed the work.

5. Reconcile with the API

If your endpoint was unavailable, an event was dropped, or processing failed permanently, use the provider API to retrieve current state. Keep a periodic reconciliation job for important data.

6. Record useful diagnostics

Log the event ID, event type, received time, processing result, retry count, and a correlation ID. Never log secret signing keys or full payment details.

Complete Node.js webhook example

The following Express example demonstrates raw-body signature verification, duplicate detection, fast acknowledgement, and asynchronous work. Replace the placeholders with values from your provider.

import express from 'express';
import Stripe from 'stripe';

const app = express();
const stripe = new Stripe(process.env.STRIPE_SECRET_KEY);
const webhookSecret = process.env.STRIPE_WEBHOOK_SECRET;
const processed = new Set(); // Use a database with a unique constraint in production.

app.post('/stripe/webhook', express.raw({ type: 'application/json' }), (req, res) => {
  const signature = req.headers['stripe-signature'];
  let event;

  try {
    event = stripe.webhooks.constructEvent(req.body, signature, webhookSecret);
  } catch (error) {
    console.error('Invalid webhook signature:', error.message);
    return res.status(400).send('Invalid signature');
  }

  if (processed.has(event.id)) {
    return res.sendStatus(200);
  }
  processed.add(event.id);

  // Queue this work in a durable job system in production.
  queueBusinessWork(event).catch((error) => {
    console.error('Webhook processing failed:', error);
  });

  return res.sendStatus(200);
});

async function queueBusinessWork(event) {
  if (event.type === 'payment_intent.succeeded') {
    const paymentIntent = event.data.object;
    // Update the order using paymentIntent.metadata.order_id.
    console.log('Payment succeeded:', paymentIntent.id);
  }
}

app.listen(3000, () => console.log('Listening on port 3000'));

Do not place express.json() before this route unless your framework preserves the raw body required by signature verification. Use durable storage instead of an in-memory Set; the in-memory example only illustrates the control flow.

Calling an API with cURL, Python, and Node.js

An API call is client initiated. This generic cURL example shows the request-response shape; replace the URL, token, and endpoint with the provider’s documented values.

curl --request GET \
  --url 'https://api.example.com/v1/resources/123' \
  --header 'Authorization: Bearer YOUR_API_TOKEN' \
  --header 'Accept: application/json'
import requests

response = requests.get(
    'https://api.example.com/v1/resources/123',
    headers={
        'Authorization': 'Bearer YOUR_API_TOKEN',
        'Accept': 'application/json',
    },
    timeout=30,
)
response.raise_for_status()
resource = response.json()
print(resource)
const response = await fetch('https://api.example.com/v1/resources/123', {
  headers: {
    Authorization: 'Bearer YOUR_API_TOKEN',
    Accept: 'application/json',
  },
});

if (!response.ok) {
  throw new Error(`API request failed: ${response.status}`);
}

const resource = await response.json();
console.log(resource);

Polling versus webhooks

Polling is straightforward: request a status at a fixed interval, compare it with the previous value, and act when it changes. Its disadvantages are repeated requests, rate-limit consumption, delayed detection between intervals, and wasted work when nothing changed.

Webhooks avoid those unnecessary checks for subscribed events, but require endpoint operations and reliable delivery handling. A practical hybrid is to receive webhooks for prompt work and run occasional API reconciliation to repair gaps.

Concern Polling Webhooks
Implementation HTTP client and scheduler Public endpoint and event processor
Idle traffic Requests continue when nothing changed No delivery until an event occurs
Failure recovery Request current state again Retry deliveries and reconcile through the API
Latency Depends on polling interval Usually near real time after the event

Security checklist

  • Require HTTPS in production.
  • Verify signatures or another provider authentication mechanism.
  • Use constant-time comparison where you implement HMAC verification yourself.
  • Reject stale timestamps when the provider includes a signed timestamp.
  • Validate event types and expected object IDs.
  • Store secrets in environment variables or a secret manager.
  • Apply rate limits and request-size limits.
  • Redact credentials and sensitive customer data from logs.
  • Use a unique event ID to prevent duplicate effects.

Performance, reliability, and cost considerations

Performance

Keep the webhook handler’s synchronous path short. Queue image processing, email, fulfillment, and other slow operations. For API clients, use connection reuse, bounded timeouts, exponential backoff for transient failures, and provider-recommended rate limits.

Reliability

Assume requests can be duplicated, delayed, rejected, or delivered while your system is restarting. Persist events before acknowledging them when losing an event would matter. Provide replay or reconciliation tooling and monitor failed deliveries.

Cost and quotas

Polling can consume API request quotas even when no state changes. Webhooks reduce those unnecessary checks, but operating a receiver adds infrastructure, storage, logging, and queue costs. Compare the provider’s request limits and webhook retention or retry behavior before choosing an architecture.

Or skip the browser setup

If your API workflow is collecting screenshots, ScreenshotNeo provides a direct HTTP API and an MCP server for AI agents. One GET request returns a PNG, JPEG, WebP, or PDF. Before capture, it accepts consent banners and removes more than 60 known consent platforms, newsletter popups, and chat widgets; each step can be turned off. Bot checks, blank pages, timeouts, failed loads, and cache hits cost nothing, and the response identifies the result with X-Page-Verdict and X-Billed headers.

See the ScreenshotNeo API documentation for parameters and configuration.

curl -G "https://api.screenshotneo.com/v1/shot" -d access_key=YOUR_API_KEY --data-urlencode url=https://stripe.com -o shot.webp
import requests
r = requests.get("https://api.screenshotneo.com/v1/shot", params={"access_key": "YOUR_API_KEY", "url": "https://stripe.com"}, timeout=90)
open("shot.webp", "wb").write(r.content)
const q = new URLSearchParams({ access_key: 'YOUR_API_KEY', url: 'https://stripe.com' }); const res = await fetch(`https://api.screenshotneo.com/v1/shot?${q}`);

ScreenshotNeo also supports asynchronous jobs with signed webhooks, bulk capture, caching with a chosen TTL, custom headers and cookies, device presets, full-page capture, element capture, PDFs, and other capture controls. Its MCP tools let Claude, Cursor, and other MCP clients call take_screenshot, get_page_info, and capture_pdf.

There are 1,000 free screenshots each month with no card. Paid plans start at $5 for 3,000 shots. Create a free ScreenshotNeo account.

Troubleshooting

The webhook returns 400 for every request

Cause: The signature is invalid, the wrong secret is configured, or middleware changed the raw body.

Fix: Confirm the endpoint secret, pass the provider’s signature header, and verify the untouched request bytes before JSON parsing.

The provider keeps retrying after a successful operation

Cause: Your endpoint performed the work but timed out or returned a non-success status.

Fix: Persist or enqueue the event, return success quickly, and make the worker idempotent.

Orders are duplicated

Cause: The same event was delivered more than once or a worker retried after a partial failure.

Fix: Store event IDs with a unique database constraint and make each business operation safe to repeat.

Events are missing

Cause: The endpoint was unreachable, the subscription filters excluded the event, or processing failed permanently.

Fix: Inspect provider delivery logs, verify subscription settings, replay eligible events, and reconcile current state through the API.

Polling hits rate limits

Cause: The polling interval is too frequent or many resources are checked independently.

Fix: Prefer webhooks for event notification, use conditional requests if supported, back off after rate-limit responses, and batch or page API reads.

FAQ

Are webhooks and APIs different technologies?

They are different interaction patterns that commonly use the same HTTP transport. An API request is started by the client; a webhook request is started by the provider.

Can a webhook replace an API?

No. A webhook tells you that an event occurred. You often still need the API to issue commands, retrieve complete details, or reconcile state.

Should a webhook contain the complete resource?

That depends on the provider. Treat the event as a notification and retrieve authoritative state through the API when completeness or freshness matters.

Do webhooks guarantee delivery?

Do not assume a guarantee unless the provider documents one. Design for retries, duplicates, outages, and reconciliation.

Which should I implement first?

Implement the API operation your product needs, then add webhooks for provider events that must trigger prompt or reliable follow-up work.