Skip to content
OneKitTools logoOneKitTools
dev6 min read

"Testing APIs and Webhooks Without Postman: A Practical Guide"

A practical guide to testing REST APIs and webhooks without Postman — request anatomy, auth patterns, capture URLs, HMAC signatures and JWT debugging.

OneKitTools TeamJuly 10, 2026

Testing APIs Without Postman: What You Actually Need

Postman grew from a lightweight REST client into a full platform — workspaces, accounts, sync, collections in the cloud. If your job is managing hundreds of requests across a team, fine. But for the daily reality of "does this endpoint work and why is it returning 401?", you need exactly two capabilities: sending arbitrary HTTP requests and inspecting what comes back (plus, for webhooks, a way to see what someone else is sending you). Both fit in a browser tab.

Anatomy of a REST Request

Every HTTP request has four parts, and most bugs live in exactly one of them:

PartWhat it isClassic mistake
MethodGET, POST, PUT, PATCH, DELETEPOSTing to a GET-only route → 405
URLEndpoint + query stringUnencoded & or space in a query param
HeadersMetadata: auth, content type, acceptMissing Content-Type: application/json
BodyThe payload (POST/PUT/PATCH)Sending form-encoded data the API expects as JSON

The equivalent in curl, the lingua franca of API documentation:

curl -X POST "https://api.example.com/v1/orders" \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer eyJhbGciOi..." \
  -d '{"product_id": 42, "quantity": 2}'

An API Tester gives you the same power with a UI: pick the method, add headers, paste the body, send. Faster to iterate than editing a curl one-liner, no install, and nothing to sync to anyone's cloud.

The three auth patterns you'll meet

Bearer token (OAuth2, JWTs, most modern APIs):

Authorization: Bearer eyJhbGciOiJIUzI1NiIs...

API key — either a header or a query param, per the API's docs:

X-API-Key: sk_live_abc123

Basic authusername:password, Base64-encoded (encoded, not encrypted — HTTPS is mandatory):

Authorization: Basic dXNlcjpwYXNzd29yZA==

Reading the response like a pro

Read in this order — status, headers, body:

  • 2xx — success. 201 means created, 204 means success with no body (don't parse it).
  • 401 vs 403 — the distinction that saves hours: 401 = "I don't know who you are" (missing/expired/malformed credentials), 403 = "I know who you are, and you're not allowed" (permissions/scopes).
  • 422 — the server understood your JSON but rejected the values. The body usually says which field.
  • 429 — rate-limited; look for a Retry-After header.
  • 5xx — their problem, not yours (usually).

Headers carry the meta-story: Content-Type tells you how to parse the body, X-RateLimit-Remaining tells you how close to the cliff you are. And when the body is a wall of minified JSON, paste it into a JSON Formatter to make the structure readable before you go blaming the wrong field.

Webhooks: Testing Traffic That Flows the Other Way

An API call is you asking a server a question. A webhook is the inverse: the server calls you when something happens — Stripe on payment success, GitHub on push, your CI on build completion. It's just an HTTP POST to a URL you registered.

That inversion is exactly what makes webhooks painful to test: the sender needs a publicly reachable URL, and localhost:3000 isn't one. You also can't set a breakpoint in Stripe's code to see what they sent.

The capture-URL workflow

The standard solution is a capture URL: a disposable public endpoint that accepts anything and shows you the full request. With a Webhook Tester the loop looks like this:

  1. Generate a unique URL — something like https://.../hook/a1b2c3.
  2. Register it as the webhook endpoint in the sending service (Stripe dashboard, GitHub repo settings, etc.).
  3. Trigger the event — make a test payment, push a commit.
  4. Inspect the capture: method, every header, and the raw body. This is the ground truth of what the sender actually sends — which regularly disagrees with what their docs say.
  5. Write your handler against reality, then replay the captured payload against your real endpoint:
curl -X POST "http://localhost:3000/webhooks/stripe" \
  -H "Content-Type: application/json" \
  -H "Stripe-Signature: t=1720598400,v1=5257a869e7..." \
  -d @captured-payload.json

Replaying beats re-triggering: you iterate on your handler in seconds instead of making a new test payment for every code change.

Verify signatures, or anyone can call your endpoint

A webhook endpoint is a public URL that mutates your system — if you don't verify authenticity, anyone who discovers the URL can forge events ("payment succeeded!"). The standard mechanism is an HMAC signature: the sender hashes the raw body with a shared secret and puts the result in a header; you recompute and compare.

const crypto = require("crypto");

const expected = crypto
  .createHmac("sha256", process.env.WEBHOOK_SECRET)
  .update(rawBody)                    // the RAW body — before any JSON parsing
  .digest("hex");

const valid = crypto.timingSafeEqual(
  Buffer.from(signatureHeader),
  Buffer.from(expected)
);

Two failure modes account for nearly every "signature mismatch" bug: computing the HMAC over the parsed-and-re-stringified body instead of the raw bytes (key order and whitespace change the hash), and framework middleware consuming the raw body before your handler sees it. Also honor the timestamp most providers embed in the signature header — reject events older than ~5 minutes to block replay attacks.

Debugging Auth with JWTs

When a request fails with 401 and the token looks fine, stop guessing and decode it. A JWT is three Base64url segments — header, payload, signature — and the first two are readable by anyone. Paste the token into a JWT Decoder and check, in order:

  1. exp — expired token, the #1 cause. It's a Unix timestamp; compare with now.
  2. iss / aud — token issued by/for a different environment (staging token against prod API is a classic).
  3. scope / roles — present but insufficient → that's your 403 explained.
  4. alg in the header — an unexpected algorithm means signature verification will fail server-side.

Building the server side of auth? Generate test tokens with chosen claims — a valid one, an expired one, one missing a scope — using a JWT Builder, and you can exercise every branch of your middleware without wiring up a real identity provider.

A Workflow That Covers 95% of Cases

  1. Reproduce the call in an API tester — isolate it from your app's code.
  2. Read status → headers → body, in that order.
  3. 401? Decode the token. 422? Format and reread the body you sent.
  4. Webhook flaky? Capture the real payload, verify the signature math, replay locally.
  5. Only then open your application code — now you know which side is lying.

Send Your First Request Now

Fire a real request from your browser with the API Tester — methods, headers, auth and formatted responses included. No account required.

Share