<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: yobox</title>
    <description>The latest articles on DEV Community by yobox (@yobox).</description>
    <link>https://dev.to/yobox</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3981137%2F2f924e03-ddcd-497c-b085-cb7a2dd8dd03.png</url>
      <title>DEV Community: yobox</title>
      <link>https://dev.to/yobox</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/yobox"/>
    <language>en</language>
    <item>
      <title>Webhook Testing Without ngrok</title>
      <dc:creator>yobox</dc:creator>
      <pubDate>Fri, 28 Aug 2026 13:13:10 +0000</pubDate>
      <link>https://dev.to/yobox/webhook-testing-without-ngrok-58jj</link>
      <guid>https://dev.to/yobox/webhook-testing-without-ngrok-58jj</guid>
      <description>&lt;p&gt;ngrok is great but overkill when you just want to see what Stripe or GitHub is sending. A hosted webhook inspector gives you a URL, a live request log, and zero setup.&lt;/p&gt;

&lt;h1&gt;
  
  
  Workflow
&lt;/h1&gt;

&lt;p&gt;Open YoBox's Webhook Tester.&lt;br&gt;
Copy the unique URL it generates.&lt;br&gt;
Paste it into the third-party service's webhook field.&lt;br&gt;
Trigger the event. Watch headers and body stream into the log.&lt;/p&gt;

&lt;h1&gt;
  
  
  When to graduate
&lt;/h1&gt;

&lt;p&gt;Once you've inspected the payload shape, switch to a local tunnel (ngrok, cloudflared) so your dev server can actually respond. Webhook inspectors are for understanding, not handling.&lt;/p&gt;

&lt;h1&gt;
  
  
  Why "without ngrok" is the right default
&lt;/h1&gt;

&lt;p&gt;ngrok is an excellent tool, but it solves a slightly different problem than most developers think. ngrok is for handling an inbound request on your laptop — your local Express app actually responds. A hosted webhook inspector is for understanding the request — you want to see what Stripe, GitHub, Shopify, or Clerk is actually sending before you bother writing handler code.&lt;/p&gt;

&lt;p&gt;Most webhook integrations start with the second problem, not the first. You do not yet know what fields the payload contains, what headers are signed, or how the provider retries on failure. Solving "see the payload" with ngrok is overkill and slow: you have to install a binary, authenticate, expose your machine, and start a server that does nothing but console.log(req.body).&lt;/p&gt;

&lt;p&gt;If you only need to read a payload, a hosted inspector wins. If you need to respond in a way that affects the provider's behavior, a tunnel wins. Most integrations need the inspector first and the tunnel later.&lt;/p&gt;

&lt;h1&gt;
  
  
  A clean workflow with the YoBox Webhook Tester
&lt;/h1&gt;

&lt;p&gt;The YoBox Webhook Tester gives you a unique URL, a live request log, and zero install steps. The end-to-end loop:&lt;/p&gt;

&lt;p&gt;Open the tool. A unique endpoint is generated immediately.&lt;br&gt;
Copy that URL into the provider's webhook configuration (Stripe, GitHub, Linear, Clerk, etc.).&lt;br&gt;
Trigger the event from the provider — a test webhook, a real signup, a push to a branch.&lt;br&gt;
Watch the request appear in the YoBox log, with full headers, body, and timing.&lt;br&gt;
Copy a real payload from the log into your unit tests so your handler is exercised with realistic data.&lt;br&gt;
That five-step loop replaces fifteen minutes of ngrok wiring on day one of a new integration.&lt;/p&gt;

&lt;h1&gt;
  
  
  Webhook inspector vs. tunnel vs. mock server
&lt;/h1&gt;

&lt;p&gt;┌─────────────────────────────────────────┐&lt;br&gt;
│ 📦 YoBox Webhook Tester             │&lt;br&gt;
├─────────────────────────────────────────┤&lt;br&gt;
│ • Inbound requests:          ✅ Yes  │&lt;br&gt;
│ • Local handler responds:    ❌ No   │&lt;br&gt;
│ • Replay:                    ⚠️ Manual│&lt;br&gt;
│ • Best for: Inspecting payloads,    │&lt;br&gt;
│   capturing fixtures                    │&lt;br&gt;
└─────────────────────────────────────────┘&lt;/p&gt;

&lt;p&gt;┌─────────────────────────────────────────┐&lt;br&gt;
│ 📦 ngrok / cloudflared               │&lt;br&gt;
├─────────────────────────────────────────┤&lt;br&gt;
│ • Inbound requests:          ✅ Yes  │&lt;br&gt;
│ • Local handler responds:    ✅ Yes  │&lt;br&gt;
│ • Replay:                    ❌ No   │&lt;br&gt;
│ • Best for: Live handler            │&lt;br&gt;
│   development                           │&lt;br&gt;
└─────────────────────────────────────────┘&lt;/p&gt;

&lt;p&gt;┌─────────────────────────────────────────┐&lt;br&gt;
│ 📦 webhook.site                     │&lt;br&gt;
├─────────────────────────────────────────┤&lt;br&gt;
│ • Inbound requests:          ✅ Yes  │&lt;br&gt;
│ • Local handler responds:    ⚠️ Limited│&lt;br&gt;
│ • Replay:                    ⚠️ Manual│&lt;br&gt;
│ • Best for: Same as YoBox,          │&lt;br&gt;
│   third-party                           │&lt;br&gt;
└─────────────────────────────────────────┘&lt;/p&gt;

&lt;p&gt;┌─────────────────────────────────────────┐&lt;br&gt;
│ 📦 Mock server (msw, Prism)        │&lt;br&gt;
├─────────────────────────────────────────┤&lt;br&gt;
│ • Inbound requests:          ❌ No (outbound only) │&lt;br&gt;
│ • Local handler responds:    N/A    │&lt;br&gt;
│ • Replay:                    ✅ Yes  │&lt;br&gt;
│ • Best for: Testing your code in    │&lt;br&gt;
│   isolation                             │&lt;br&gt;
└─────────────────────────────────────────┘&lt;/p&gt;

&lt;h1&gt;
  
  
  Integrating with your test suite
&lt;/h1&gt;

&lt;p&gt;Once you have a captured payload, the next step is shipping reliable webhook tests. Two patterns work well.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Snapshot the payload, replay in unit tests
Save the JSON body to fixtures/stripe-customer-created.json and feed it into your handler in a Vitest or Jest test. You get the real shape without depending on Stripe's network in CI.
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;handleStripeWebhook&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;../src/webhooks/stripe&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;fixture&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;./fixtures/stripe-customer-created.json&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="nf"&gt;it&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;creates a local user when Stripe customer.created fires&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;handleStripeWebhook&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{},&lt;/span&gt; &lt;span class="na"&gt;body&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;fixture&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="nf"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;status&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;toBe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ol&gt;
&lt;li&gt;End-to-end with Cypress or Playwright
For signup-to-webhook flows, combine the inspector with YoBox Temp Mail. A worked example is in Cypress E2E with YoBox and Playwright Automation with YoBox.&lt;/li&gt;
&lt;/ol&gt;

&lt;h1&gt;
  
  
  When you do need ngrok
&lt;/h1&gt;

&lt;p&gt;There is a clear moment to graduate from inspector to tunnel:&lt;/p&gt;

&lt;p&gt;The provider requires a 2xx response within N seconds and you want to verify your handler meets the deadline.&lt;br&gt;
You need to test idempotency by responding with a non-2xx and watching the provider retry.&lt;br&gt;
You are debugging a signature verification function and need to feed it the raw, unmodified body your server receives.&lt;br&gt;
For those, run ngrok http 3000 (or cloudflared tunnel) and point the provider at the tunnel URL. The inspector workflow has already taught you what to expect, so wiring the handler takes minutes instead of hours.&lt;/p&gt;

&lt;h1&gt;
  
  
  Provider-specific notes
&lt;/h1&gt;

&lt;p&gt;Stripe&lt;br&gt;
Use the Stripe CLI for local event triggering, but use YoBox when you want to see what production-shaped events look like (CLI events are slightly simplified). Verify signatures by capturing the Stripe-Signature header from a real event.&lt;/p&gt;

&lt;p&gt;GitHub&lt;br&gt;
GitHub webhooks include X-Hub-Signature-256 and a delivery ID. Capturing both lets you replay deliveries deterministically and write signature-verification tests against realistic data.&lt;/p&gt;

&lt;p&gt;Clerk, Auth0, WorkOS&lt;br&gt;
Auth providers send sensitive events (user.created, session.revoked). A disposable inspector URL keeps those payloads off any shared logging infrastructure during early integration work.&lt;/p&gt;

&lt;p&gt;Shopify&lt;br&gt;
Shopify retries aggressively. Inspecting payloads before writing your handler tells you exactly which topics are noisy and which deserve idempotency keys.&lt;/p&gt;

&lt;h1&gt;
  
  
  Key takeaways
&lt;/h1&gt;

&lt;p&gt;Use a hosted inspector to understand a webhook; use a tunnel to handle it.&lt;br&gt;
Capture real payloads early and freeze them as test fixtures.&lt;br&gt;
Combine the Webhook Tester with Temp Mail for end-to-end auth flow debugging.&lt;br&gt;
Graduate to ngrok or cloudflared only when you need the provider to see your response.&lt;/p&gt;

&lt;h1&gt;
  
  
  Real use cases
&lt;/h1&gt;

&lt;p&gt;Auditing third-party providers before signing a contract&lt;br&gt;
Before committing to a SaaS, point its webhook at YoBox and trigger every event type. You learn the real payload quality in fifteen minutes.&lt;/p&gt;

&lt;p&gt;Debugging missing fields in production&lt;br&gt;
When a payload field "sometimes" goes missing, ask the provider to copy a webhook to the inspector URL. Compare side-by-side with what your production logs captured.&lt;/p&gt;

&lt;p&gt;Onboarding new engineers&lt;br&gt;
Hand a junior engineer the inspector URL on day one. They learn the integration's vocabulary by watching live traffic, not by reading docs.&lt;/p&gt;

&lt;h1&gt;
  
  
  FAQ
&lt;/h1&gt;

&lt;p&gt;Do I need an account?&lt;br&gt;
No. The Webhook Tester generates a URL instantly with no signup.&lt;/p&gt;

&lt;p&gt;How long does the URL stay alive?&lt;br&gt;
As long as the tab is open. For longer captures, keep the tab pinned or rotate URLs daily.&lt;/p&gt;

&lt;p&gt;Can I respond with a custom status code?&lt;br&gt;
The hosted inspector returns 200. For custom responses, switch to a tunnel.&lt;/p&gt;

&lt;p&gt;Is the payload stored anywhere?&lt;br&gt;
Requests live in your browser session. There is no database backing the inspector.&lt;/p&gt;

&lt;p&gt;What about HMAC signature verification?&lt;br&gt;
Capture the signature header from a real request, then use it in your unit tests against the same body. See the Stripe and GitHub notes above.&lt;/p&gt;

&lt;h1&gt;
  
  
  A short checklist for new webhook integrations
&lt;/h1&gt;

&lt;p&gt;Before writing a single line of handler code, run through this list. It takes ten minutes and saves hours.&lt;/p&gt;

&lt;p&gt;[ ] Generate a fresh URL in the Webhook Tester.&lt;br&gt;
[ ] Configure the provider to send to that URL.&lt;br&gt;
[ ] Trigger every event type the provider supports — not just the one you care about today.&lt;br&gt;
[ ] Inspect the headers: which ones are signed, which include a delivery ID, which include retry metadata.&lt;br&gt;
[ ] Save at least one payload per event type as a JSON fixture in your repo.&lt;br&gt;
[ ] Note the provider's retry policy (interval, max attempts, backoff).&lt;br&gt;
[ ] Note the provider's timeout (often 5–10 seconds).&lt;br&gt;
[ ] Decide on idempotency keys before you start coding.&lt;br&gt;
That last point is the one most teams skip. Almost every webhook provider retries on non-2xx responses, and almost every handler is non-idempotent on day one. Designing the idempotency story up front — usually a unique constraint on the provider's event ID — prevents a class of bugs that are extremely painful to debug in production.&lt;/p&gt;

&lt;p&gt;When the provider does not have a test event&lt;br&gt;
Some providers (older billing systems, certain ERPs) cannot trigger test events on demand. In those cases, set up the inspector URL, run a real low-value action in the provider, and capture the payload. You only need one good fixture to start writing realistic tests.&lt;/p&gt;

&lt;p&gt;Replaying captured webhooks against a local handler&lt;br&gt;
Once you have ten or twenty real payloads saved as fixtures, you can replay them against your handler with a one-line curl loop. This is the closest thing to production traffic you can get without a tunnel, and it runs in CI for free. Pair this with the Postman testing guide for assertions on the response, and you have a webhook test pipeline that catches regressions before they reach staging.&lt;/p&gt;

&lt;h1&gt;
  
  
  Conclusion
&lt;/h1&gt;

&lt;p&gt;ngrok is a great tool, but reaching for it on minute one of a new webhook integration is like firing up Docker to read a CSV. Most of the time you just want to see what the provider is sending. A hosted inspector like the YoBox Webhook Tester closes that loop in seconds, captures fixtures for your test suite, and stays out of your way until you are ready to write a real handler — at which point a tunnel is the right next step.&lt;/p&gt;

&lt;h1&gt;
  
  
  YoBox Team
&lt;/h1&gt;

&lt;p&gt;Builder behind YoBox — a privacy-first toolbox for developers and QA engineers covering disposable email, webhook capture, regex, secure passwords, Docker, and end-to-end testing.&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>api</category>
      <category>testing</category>
      <category>devops</category>
    </item>
    <item>
      <title>Temporary Email for Discord Signup: Does It Still Work in 2026?</title>
      <dc:creator>yobox</dc:creator>
      <pubDate>Tue, 25 Aug 2026 13:49:30 +0000</pubDate>
      <link>https://dev.to/yobox/temporary-email-for-discord-signup-does-it-still-work-in-2026-4d0g</link>
      <guid>https://dev.to/yobox/temporary-email-for-discord-signup-does-it-still-work-in-2026-4d0g</guid>
      <description>&lt;p&gt;Discord is one of the strictest platforms when it comes to disposable email. Their signup form checks against a large, regularly updated blocklist of known disposable domains, and most "temp mail" services are flagged within hours of going viral.&lt;/p&gt;

&lt;p&gt;That said, signing up to Discord without using your personal email is still possible — you just have to be precise about which approach you use. This guide covers what works in 2026, what doesn't, and the safer alternative if you actually want a usable Discord account.&lt;/p&gt;

&lt;h1&gt;
  
  
  Why Discord Blocks Disposable Email
&lt;/h1&gt;

&lt;p&gt;Three reasons:&lt;/p&gt;

&lt;p&gt;Spam and ban evasion. Banned users create new accounts with disposable email. Discord makes that expensive.&lt;br&gt;
Trust &amp;amp; safety. Sketchy users come in via disposable domains; gating signup is the cheapest filter.&lt;br&gt;
Compliance. Verified email helps Discord respond to abuse reports, subpoenas, and account recovery.&lt;br&gt;
The blocklist is automated. Domains go on it based on traffic patterns and community reports. Popular temp mail domains are usually flagged within days.&lt;/p&gt;

&lt;h1&gt;
  
  
  What Doesn't Work
&lt;/h1&gt;

&lt;p&gt;If you're hoping these work, save yourself the time:&lt;/p&gt;

&lt;p&gt;temp-mail.org domains. Blocked.&lt;br&gt;
mail.tm domains. Mostly blocked.&lt;br&gt;
10minutemail. Blocked.&lt;br&gt;
Guerrilla Mail. Blocked.&lt;br&gt;
Most free YouTube-tutorial-recommended services. Blocked.&lt;br&gt;
You'll go through the entire signup flow, Discord will say it sent an OTP, no email will arrive, and you'll be stuck.&lt;/p&gt;

&lt;h1&gt;
  
  
  What Sometimes Works
&lt;/h1&gt;

&lt;p&gt;Some approaches survive longer than others:&lt;/p&gt;

&lt;p&gt;Rotating-domain providers&lt;br&gt;
A few temp mail services rotate through fresh, less-known domains. These last longer because the blocklist hasn't caught up. The YoBox Temp Mail tool uses a rotating upstream pool that tends to slip through more often than fixed-domain services.&lt;/p&gt;

&lt;p&gt;"Sometimes" is doing a lot of work in that sentence. Don't bet on it for anything important.&lt;/p&gt;

&lt;p&gt;Self-owned domains&lt;/p&gt;

&lt;p&gt;If you own a domain and configure a catch-all forwarder, Discord can't blocklist you — you're not on any public list. This is the most reliable approach for power users.&lt;/p&gt;

&lt;h1&gt;
  
  
  What Works Reliably
&lt;/h1&gt;

&lt;p&gt;If you actually want a usable Discord account without using your personal Gmail, use an email alias service. The big ones — SimpleLogin, AnonAddy, Apple Hide My Email — generate permanent forwarding addresses that look like real email and (mostly) aren't on Discord's blocklist.&lt;/p&gt;

&lt;p&gt;Aliases give you:&lt;/p&gt;

&lt;p&gt;A permanent address that survives blocklisting&lt;br&gt;
Forwarding to your real inbox (so OTPs and recovery work)&lt;br&gt;
The ability to kill the alias later if needed&lt;br&gt;
For most users, alias &amp;gt; disposable for Discord.&lt;/p&gt;

&lt;p&gt;See "Disposable Email vs Real Email vs Aliases" for the full breakdown.&lt;/p&gt;

&lt;h1&gt;
  
  
  The Real Trade-Off
&lt;/h1&gt;

&lt;p&gt;You're choosing between:&lt;/p&gt;

&lt;p&gt;┌─────────────────────────────────────────┐&lt;br&gt;
│ 📦 Personal Gmail                   │&lt;br&gt;
├─────────────────────────────────────────┤&lt;br&gt;
│ • Works on Discord?          ✅ Yes  │&lt;br&gt;
│ • Account survives long-term? ✅ Yes  │&lt;br&gt;
│ • Effort:                     None  │&lt;br&gt;
└─────────────────────────────────────────┘&lt;/p&gt;

&lt;p&gt;┌─────────────────────────────────────────┐&lt;br&gt;
│ 📦 Email alias (SimpleLogin etc.) │&lt;br&gt;
├─────────────────────────────────────────┤&lt;br&gt;
│ • Works on Discord?          ⚠️ Usually│&lt;br&gt;
│ • Account survives long-term? ✅ Yes  │&lt;br&gt;
│ • Effort:         Low (one-time setup)│&lt;br&gt;
└─────────────────────────────────────────┘&lt;/p&gt;

&lt;p&gt;┌─────────────────────────────────────────┐&lt;br&gt;
│ 📦 Disposable email                 │&lt;br&gt;
├─────────────────────────────────────────┤&lt;br&gt;
│ • Works on Discord?          ❌ Rarely│&lt;br&gt;
│ • Account survives long-term? ❌ No (inbox dies)│&lt;br&gt;
│ • Effort:                     None  │&lt;br&gt;
└─────────────────────────────────────────┘&lt;/p&gt;

&lt;p&gt;┌─────────────────────────────────────────┐&lt;br&gt;
│ 📦 Own domain + catch-all           │&lt;br&gt;
├─────────────────────────────────────────┤&lt;br&gt;
│ • Works on Discord?          ✅ Yes  │&lt;br&gt;
│ • Account survives long-term? ✅ Yes  │&lt;br&gt;
│ • Effort:               High (setup)│&lt;br&gt;
└─────────────────────────────────────────┘&lt;br&gt;
For most people, the alias approach is the sweet spot.&lt;/p&gt;

&lt;h1&gt;
  
  
  If You Do Use Disposable Email
&lt;/h1&gt;

&lt;p&gt;If you're going for it anyway:&lt;/p&gt;

&lt;p&gt;Use a service with rotating domains. YoBox Temp Mail is a reasonable starting point.&lt;br&gt;
Have the inbox open before you submit. If the OTP arrives, you need to use it within 10 minutes.&lt;br&gt;
Watch for silent rejection. If Discord says "we sent a code" but nothing arrives in 60 seconds, the address was probably rejected. Try a new one.&lt;br&gt;
Expect the account to be flagged. Disposable signups on Discord often face additional verification (phone, IP, behavior) within the first few days. See "Why OTP Verification Fails".&lt;br&gt;
Save the address. If you want to log back in, you'll need to be able to receive password reset emails.&lt;/p&gt;

&lt;h1&gt;
  
  
  A Note on Phone Verification
&lt;/h1&gt;

&lt;p&gt;Even if you get past the email blocklist, Discord may require phone verification — especially if you're joining a large public server, sending DMs to strangers, or hitting any of their many trust signals. Disposable phone numbers face the same blocklist problem as disposable email, only worse.&lt;/p&gt;

&lt;p&gt;If you need long-term account stability on Discord, eventually you'll need a phone number Discord trusts. There's no good workaround.&lt;/p&gt;

&lt;h1&gt;
  
  
  Why You Might Actually Want to Use Real Email
&lt;/h1&gt;

&lt;p&gt;Honest take: for most use cases, the friction of fighting Discord's filters isn't worth it. If you're:&lt;/p&gt;

&lt;p&gt;Joining a community you'll engage with for months&lt;br&gt;
Building a bot&lt;br&gt;
Running a server&lt;br&gt;
DM'ing with people you'll continue to talk to&lt;br&gt;
…the right move is to use a real email (or an alias that forwards to one), accept the phone verification, and treat Discord like a real account.&lt;/p&gt;

&lt;p&gt;Save disposable email for the cases where it actually fits — one-off signups, downloads, testing your own apps. The YoBox Temp Mail tool is brilliant for those; it's just not the right tool for Discord.&lt;/p&gt;

&lt;h1&gt;
  
  
  Developer Note
&lt;/h1&gt;

&lt;p&gt;If you're building a Discord bot and need to test OAuth flows or webhook integrations, you don't need a fresh Discord account per test — you can use a single dev account and rotate roles, channels, or test servers. For inspecting Discord's webhooks (e.g. MESSAGE_CREATE events), pair the bot with the YoBox Webhook Tester to see exactly what Discord sends.&lt;/p&gt;

&lt;h1&gt;
  
  
  FAQ
&lt;/h1&gt;

&lt;p&gt;What's the best temp mail for Discord in 2026?&lt;br&gt;
None reliably. Use an alias service (SimpleLogin, Apple Hide My Email) instead.&lt;/p&gt;

&lt;p&gt;Why does my Discord OTP never arrive?&lt;br&gt;
Most likely: Discord silently rejected your disposable address. See "Why OTP Verification Fails".&lt;/p&gt;

&lt;p&gt;Can I make a Discord account without any email?&lt;br&gt;
No. Email is required.&lt;/p&gt;

&lt;p&gt;Will my Discord account get banned for using a temp email?&lt;br&gt;
The account itself usually isn't banned just for the email, but it's flagged for additional verification (phone, behavior). You might lose access if you can't pass verification.&lt;/p&gt;

&lt;p&gt;Is there a "Discord-compatible" temp mail?&lt;br&gt;
No service is permanently Discord-compatible. The blocklist catches up.&lt;/p&gt;

&lt;h1&gt;
  
  
  Bottom Line
&lt;/h1&gt;

&lt;p&gt;Disposable email and Discord don't get along, and that gap is widening. In 2026, the reliable path to a Discord account without your personal Gmail is an email alias service. Save temp mail for the use cases it's actually good for — one-off signups, downloads, and testing your own software.&lt;/p&gt;

&lt;h1&gt;
  
  
  YoBox Team
&lt;/h1&gt;

&lt;p&gt;Builder behind YoBox — a privacy-first toolbox for developers and QA engineers covering disposable email, webhook capture, regex, secure passwords, Docker, and end-to-end testing.&lt;/p&gt;

</description>
      <category>security</category>
      <category>discord</category>
      <category>privacy</category>
      <category>productivity</category>
    </item>
    <item>
      <title>Why Disposable Email Still Matters in 2026</title>
      <dc:creator>yobox</dc:creator>
      <pubDate>Sat, 22 Aug 2026 16:34:20 +0000</pubDate>
      <link>https://dev.to/yobox/why-disposable-email-still-matters-in-2026-174n</link>
      <guid>https://dev.to/yobox/why-disposable-email-still-matters-in-2026-174n</guid>
      <description>&lt;p&gt;Every developer has felt the friction: you're testing a new SaaS integration, debugging an OAuth flow, or QAing a signup funnel — and your real inbox fills up with welcome emails, password resets, and weekly digests you never asked for.&lt;/p&gt;

&lt;h1&gt;
  
  
  The case for disposable email
&lt;/h1&gt;

&lt;p&gt;A disposable inbox solves three real problems:&lt;/p&gt;

&lt;p&gt;Signal-to-noise. Your primary inbox stays focused on actual humans.&lt;br&gt;
Privacy. When a side-project shuts down (or gets breached), your real address isn't in the leak.&lt;br&gt;
Iteration speed. QA flows that require fresh accounts go 10x faster when provisioning takes one click.&lt;/p&gt;

&lt;h1&gt;
  
  
  What's changed since 2020
&lt;/h1&gt;

&lt;p&gt;In 2026, most major SaaS platforms have dropped naive domain blocklists. Combined with infrastructure like mail.gw, disposable email is now reliable for almost every signup.&lt;/p&gt;

&lt;h1&gt;
  
  
  How YoBox does it
&lt;/h1&gt;

&lt;p&gt;YoBox provisions a real, IMAP-backed inbox via the mail.gw open API, exposes a clean dark-themed UI, and polls for new mail every 5 seconds. Hit Delete and rotate to a fresh address in one click. Try it on the homepage — no signup required.&lt;/p&gt;

&lt;h1&gt;
  
  
  A short history of the disposable inbox
&lt;/h1&gt;

&lt;p&gt;Disposable email is older than most modern SaaS. The first throwaway addresses appeared in the late 1990s, when forum signups exploded and spammers learned to scrape "mailto:" links faster than humans could read them. For two decades the category stagnated: ugly UIs, unreliable delivery, and a constant cat-and-mouse with domain blocklists. What changed in the 2020s is infrastructure. Open mail APIs like mail.gw, paired with cheap static hosting, made it possible to ship a polished disposable inbox as a frontend-only app — no servers, no database, no signups.&lt;/p&gt;

&lt;p&gt;That shift matters because it moved disposable email out of the "shady" bucket and into the "developer utility" bucket, right next to JSON formatters and regex testers.&lt;/p&gt;

&lt;h1&gt;
  
  
  Who actually uses disposable email in 2026
&lt;/h1&gt;

&lt;p&gt;It is not just people dodging marketing newsletters. Real users include:&lt;/p&gt;

&lt;p&gt;Developers verifying OAuth, SSO, and OTP flows during integration work.&lt;br&gt;
QA engineers running signup regressions where each test needs a fresh inbox.&lt;br&gt;
Indie founders spinning up burner accounts on competitor tools to study onboarding.&lt;br&gt;
Security researchers triaging phishing payloads in isolation.&lt;br&gt;
Privacy-conscious consumers signing up for one-off downloads, coupons, or trials.&lt;br&gt;
A disposable inbox is the email equivalent of an incognito tab. It is not about hiding — it is about not leaving a permanent trail for a transaction that is, by design, temporary.&lt;/p&gt;

&lt;h1&gt;
  
  
  Disposable vs. forwarding vs. alias services
&lt;/h1&gt;

&lt;p&gt;There are three categories that often get conflated. They are not the same.&lt;/p&gt;

&lt;p&gt;+------------------+------------------+---------------+---------------&lt;br&gt;
| Type             | Example          | Lifetime      | Best for              |&lt;br&gt;
+------------------+------------------+---------------+---------------&lt;br&gt;
| Disposable       | YoBox            | Mins / Hours  | OTP,QA,Signups      |&lt;br&gt;
| Forwarding alias | SimpleLogin      | Permanent    |Longtermaccounts    |&lt;br&gt;
| Plus-addressing  | you+app@gmail    | Permanent     | Filtering only        |&lt;br&gt;
+------------------+------------------+---------------+---------------&lt;/p&gt;

&lt;p&gt;Plus-addressing leaks your real address the moment a service strips the suffix (most do during deliverability checks). Forwarding aliases are excellent for long-term identity separation but require an account and DNS-aware setup. Disposable inboxes win when the requirement is "I need a working address for the next ten minutes and I will never use it again."&lt;/p&gt;

&lt;h1&gt;
  
  
  The developer workflow that actually saves time
&lt;/h1&gt;

&lt;p&gt;The biggest productivity unlock is not avoiding spam — it is removing inbox friction from your test loop. A typical signup flow QA session looks like this without a disposable inbox:&lt;/p&gt;

&lt;p&gt;Open password manager, copy a fresh email alias.&lt;br&gt;
Submit signup.&lt;br&gt;
Tab away to your real inbox.&lt;br&gt;
Wait for the message, scroll past unrelated mail, find the OTP.&lt;br&gt;
Tab back. Paste. Repeat for the next test.&lt;br&gt;
With a disposable inbox open in a side panel, steps 3 and 4 collapse into a two-second glance. Run this loop forty times during a release week and you have recovered a full working hour.&lt;/p&gt;

&lt;p&gt;Pair with the YoBox Webhook Tester&lt;br&gt;
For signup flows that fire downstream webhooks — Stripe customer creation, Segment identify calls, CRM enrichment — combine Temp Mail with the Webhook Tester. Point the webhook URL at YoBox, trigger a signup, and watch both the inbound OTP and the outbound webhook in real time. That single screen replaces three browser tabs and an ngrok terminal.&lt;/p&gt;

&lt;h1&gt;
  
  
  SEO-friendly long-tail intents people search
&lt;/h1&gt;

&lt;p&gt;Real users land on disposable-email content through queries like:&lt;/p&gt;

&lt;p&gt;"temporary email for discord verification"&lt;br&gt;
"disposable email that works with github"&lt;br&gt;
"best temp mail for otp 2026"&lt;br&gt;
"throwaway email for developer signups"&lt;br&gt;
"anonymous email for testing"&lt;br&gt;
If you are a developer evaluating tools, all five of those queries should funnel you toward the same answer: an inbox you can spin up in one click, with a real address that survives modern deliverability checks. YoBox is built for exactly that path.&lt;/p&gt;

&lt;h1&gt;
  
  
  Common myths, retired
&lt;/h1&gt;

&lt;p&gt;"Disposable email is banned everywhere."&lt;/p&gt;

&lt;p&gt;Not in 2026. Major SaaS platforms quietly dropped blanket blocklists years ago because they were blocking legitimate corporate domains by accident. Most now use behavioral signals instead.&lt;/p&gt;

&lt;p&gt;"Throwaway inboxes never receive OTPs."&lt;/p&gt;

&lt;p&gt;Modern providers — including the open APIs YoBox is built on — pass SPF, DKIM, and DMARC checks. OTP delivery from Stripe, GitHub, Discord, OpenAI, and Vercel works reliably in our daily testing.&lt;/p&gt;

&lt;p&gt;"If it is free it must be selling my data."&lt;/p&gt;

&lt;p&gt;A frontend-only disposable mail service has nothing to sell. There is no account, no tracking pixel, and no server-side log tied to your identity. The mail provider sees an anonymous inbox; YoBox never sees your IP-to-inbox mapping because there is no backend to store it.&lt;/p&gt;

&lt;h1&gt;
  
  
  Key takeaways
&lt;/h1&gt;

&lt;p&gt;Disposable email is now a mainstream developer utility, not a gray-area trick.&lt;br&gt;
It is the fastest way to test OTP and signup flows without polluting a real inbox.&lt;br&gt;
Pair it with a webhook inspector for end-to-end auth flow debugging.&lt;br&gt;
Use forwarding aliases for long-term identity, disposable inboxes for short-term tasks.&lt;br&gt;
Prefer frontend-only providers — they cannot leak data they never collect.&lt;/p&gt;

&lt;h1&gt;
  
  
  Real use cases
&lt;/h1&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;Verifying OAuth providers in a fresh state&lt;br&gt;
When a third-party login provider caches consent, your tests stop reflecting the first-time-user path. A fresh disposable inbox forces a brand-new account every run.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Reviewing competitor onboarding&lt;br&gt;
Most growth teams audit competitor signup funnels quarterly. A disposable inbox lets you sign up, capture every onboarding email, and unsubscribe without polluting your work address.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Sandboxing newsletter signups for research&lt;br&gt;
Journalists, analysts, and indie hackers all need to subscribe to newsletters they will never read again after a story or post ships.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Bug bounty and security research&lt;br&gt;
Receiving a suspect link in a dedicated, ephemeral inbox keeps your primary identity isolated from any payload.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;h1&gt;
  
  
  FAQ
&lt;/h1&gt;

&lt;p&gt;Is disposable email legal?&lt;br&gt;
Yes, everywhere we operate. It is no different from using a Gmail alias for a single signup.&lt;/p&gt;

&lt;p&gt;Will my OTP arrive instantly?&lt;br&gt;
Usually within five seconds. YoBox polls the mailbox every five seconds and renders new messages immediately.&lt;/p&gt;

&lt;p&gt;Can I keep the same address across sessions?&lt;br&gt;
Yes — copy the address while the tab is open. If you close the tab, the inbox is gone. That is the point.&lt;/p&gt;

&lt;p&gt;Does YoBox store my emails?&lt;br&gt;
No. Messages live only at the mail provider and are fetched on demand by your browser.&lt;/p&gt;

&lt;p&gt;How is this different from a 10-minute mail service?&lt;br&gt;
Most "10 minute" services delete inboxes on a fixed timer. YoBox keeps the inbox alive as long as the tab is open and lets you rotate manually, which fits a QA workflow better.&lt;/p&gt;

&lt;h1&gt;
  
  
  How disposable email fits into a modern privacy stack
&lt;/h1&gt;

&lt;p&gt;Disposable inboxes are not a replacement for a forwarding alias service or a password manager — they sit alongside them. A healthy 2026 privacy stack usually looks like this:&lt;/p&gt;

&lt;p&gt;Password manager for credential storage and TOTP backup.&lt;br&gt;
Forwarding aliases (SimpleLogin, AnonAddy, iCloud Hide My Email) for accounts you intend to keep.&lt;br&gt;
Disposable inboxes like YoBox Temp Mail for accounts you do not intend to keep, and for QA work.&lt;br&gt;
Webhook inspector like the YoBox Webhook Tester for auditing what services send to you after signup.&lt;br&gt;
The disposable layer is the cheapest to adopt and the highest leverage for developers. You do not need to migrate anything; you just stop using your real address for transactions that do not deserve it.&lt;/p&gt;

&lt;p&gt;Browser hygiene that pairs well&lt;br&gt;
Use a container tab (Firefox Multi-Account Containers) or a fresh profile when signing up with a disposable inbox. That keeps cookies, fingerprints, and form-fill history isolated from your daily browsing identity. Combined with a disposable email, you get a clean session that leaves no permanent footprint.&lt;/p&gt;

&lt;p&gt;What to do with the OTP after the signup&lt;br&gt;
For QA work, copy the OTP from Temp Mail directly into your test runner — there is no need to retype it. For one-off signups, treat the inbox as ephemeral and close the tab when you are done; there is nothing to clean up because nothing was ever stored on your machine.&lt;/p&gt;

&lt;h1&gt;
  
  
  Conclusion
&lt;/h1&gt;

&lt;p&gt;Disposable email in 2026 is no longer a workaround — it is a first-class developer tool, on par with a REST client or a JSON formatter. The combination of reliable delivery, polished UIs, and zero-friction provisioning means there is no longer any reason to use your real address for a transaction that should not outlive a coffee break. Open YoBox Temp Mail, grab an address, and stop letting throwaway signups own a permanent seat in your inbox.&lt;/p&gt;

&lt;h1&gt;
  
  
  YoBox Team
&lt;/h1&gt;

&lt;p&gt;Builder behind YoBox — a privacy-first toolbox for developers and QA engineers covering disposable email, webhook capture, regex, secure passwords, Docker, and end-to-end testing.&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>testing</category>
      <category>productivity</category>
      <category>privacy</category>
    </item>
    <item>
      <title>Webhook.site Alternatives: 8 Tools Compared (2026)</title>
      <dc:creator>yobox</dc:creator>
      <pubDate>Thu, 20 Aug 2026 13:15:14 +0000</pubDate>
      <link>https://dev.to/yobox/webhooksite-alternatives-8-tools-compared-2026-dgp</link>
      <guid>https://dev.to/yobox/webhooksite-alternatives-8-tools-compared-2026-dgp</guid>
      <description>&lt;p&gt;Webhook.site is the tool most developers reach for when they need to inspect an incoming webhook. It's good. It's also the only tool many developers have ever tried. In 2026 there's a real ecosystem of webhook inspectors, request loggers, and capture services — each with different trade-offs on retention, pricing, security, API access, and team features.&lt;/p&gt;

&lt;p&gt;This guide compares the eight alternatives that are actually worth knowing about, with honest verdicts on which one to use when.&lt;/p&gt;

&lt;h1&gt;
  
  
  What to Look For
&lt;/h1&gt;

&lt;p&gt;Before the comparison, the dimensions that actually matter when you pick a webhook tool:&lt;/p&gt;

&lt;p&gt;Retention. Free tier wipes after X hours/days. How long do you actually need?&lt;br&gt;
Custom responses. Can you return a specific status, body, headers, or even a delay?&lt;br&gt;
API access. Can you script it from CI / Cypress / Playwright?&lt;br&gt;
Privacy. Is the URL guessable? Is your data public?&lt;br&gt;
Pricing. What does the team / pro tier cost?&lt;br&gt;
Self-hosting. Open source? Can you run it yourself?&lt;br&gt;
Integrations. Slack / Discord / email notifications on incoming requests?&lt;/p&gt;

&lt;h1&gt;
  
  
  The Comparison
&lt;/h1&gt;

&lt;p&gt;+--------------------------+-------------------+-----------------+-----+------------+-------------------+&lt;br&gt;
| Tool                     | Free Retention    | Custom Response | API | Self-Host  | Price (Pro)       |&lt;br&gt;
+--------------------------+-------------------+-----------------+-----+------------+-------------------+&lt;br&gt;
| Webhook.site             | 7 days            | Yes             | Yes | Yes (OSS)  | $19/mo            |&lt;br&gt;
| YoBox Webhook Tester     | Session           | Yes             | Yes | No         | Free              |&lt;br&gt;
| RequestBin (Pipedream)   | Limited           | Limited         | Yes | No         | Tied to Pipedream |&lt;br&gt;
| Beeceptor                | 50 req/day        | Yes             | Yes | No         | $10/mo            |&lt;br&gt;
| Hookbin                  | Hours             | Limited         | No  | No         | N/A               |&lt;br&gt;
| Mockbin                  | Hours             | Yes             | Yes | Yes (OSS)  | Free              |&lt;br&gt;
| Postman Mock Server      | Tied to workspace | Yes             | Yes | No         | Tied to Postman   |&lt;br&gt;
| ngrok inspector          | Live only         | Your server     | N/A | No         | Tied to ngrok     |&lt;br&gt;
+--------------------------+-------------------+-----------------+-----+------------+-------------------+&lt;/p&gt;

&lt;h1&gt;
  
  
  1. Webhook.site — The Default
&lt;/h1&gt;

&lt;p&gt;Webhook.site is the most-recommended tool in the space for good reason. Generous free tier, custom response builder with templating, an API for scripting, even a "CLI" mode for forwarding requests to localhost. Open-source backend if you want to self-host.&lt;/p&gt;

&lt;p&gt;Best for: general-purpose webhook inspection, anything you'd find on Stack Overflow.&lt;/p&gt;

&lt;p&gt;Watch out for: the free tier wipes after 7 days, and the URL is easily guessable.&lt;/p&gt;

&lt;h1&gt;
  
  
  2. YoBox Webhook Tester — Free, Fast, Developer-First
&lt;/h1&gt;

&lt;p&gt;The YoBox Webhook Tester is what we built because we wanted something with no signup, no ads, no tracking, and a clean JSON API that works in CI. You generate a URL, captured requests stream into the page in under 3 seconds, and the same data is available via API.&lt;/p&gt;

&lt;p&gt;Best for: developers who want a clean tool, automated tests, and pairing with disposable email for end-to-end coverage of signup flows.&lt;/p&gt;

&lt;p&gt;Watch out for: no built-in custom response templating yet (vs Webhook.site's full templating engine). The default returns 200 with a captured-by message.&lt;/p&gt;

&lt;h1&gt;
  
  
  3. RequestBin (Pipedream)
&lt;/h1&gt;

&lt;p&gt;The original RequestBin was killed years ago and reborn under Pipedream's umbrella. It's tightly integrated with Pipedream workflows — every captured request can trigger a workflow that fans out to anything. Great if you're already in the Pipedream ecosystem.&lt;/p&gt;

&lt;p&gt;Best for: users who want to act on webhooks, not just inspect them.&lt;/p&gt;

&lt;p&gt;Watch out for: standalone usage is awkward; you're really using it as a Pipedream trigger.&lt;/p&gt;

&lt;h1&gt;
  
  
  4. Beeceptor
&lt;/h1&gt;

&lt;p&gt;Beeceptor's superpower is mock APIs — you define endpoints with rules and responses. It happens to also capture incoming webhooks. Great for mocking the API you're about to integrate with.&lt;/p&gt;

&lt;p&gt;Best for: API mocking that doubles as webhook capture.&lt;/p&gt;

&lt;p&gt;Watch out for: 50 request/day free tier is tight if you're testing actively.&lt;/p&gt;

&lt;h1&gt;
  
  
  5. Hookbin
&lt;/h1&gt;

&lt;p&gt;Minimal, free, no signup. Captures requests, shows them in a list. That's it. Sometimes that's all you need.&lt;/p&gt;

&lt;p&gt;Best for: quick one-off inspection, no commitment.&lt;/p&gt;

&lt;p&gt;Watch out for: no API, no team features, no retention guarantees.&lt;/p&gt;

&lt;h1&gt;
  
  
  6. Mockbin
&lt;/h1&gt;

&lt;p&gt;Open-source, can be self-hosted, supports both mock APIs and inspectors. Maintained by the Kong team. Solid choice if you want to run your own.&lt;/p&gt;

&lt;p&gt;Best for: self-hosted webhook capture inside your own infrastructure.&lt;/p&gt;

&lt;p&gt;Watch out for: hosted version is sometimes flaky.&lt;/p&gt;

&lt;h1&gt;
  
  
  7. Postman Mock Server
&lt;/h1&gt;

&lt;p&gt;If you live in Postman, this is the path of least resistance. Define a collection, generate a mock server, point your webhook sender at it. Bonus: every request becomes a Postman example you can re-run.&lt;/p&gt;

&lt;p&gt;Best for: teams already standardized on Postman.&lt;/p&gt;

&lt;p&gt;Watch out for: mostly designed for mocking responses, not inspecting incoming requests.&lt;/p&gt;

&lt;h1&gt;
  
  
  8. ngrok Inspector
&lt;/h1&gt;

&lt;p&gt;ngrok's traffic inspector at &lt;a href="http://localhost:4040" rel="noopener noreferrer"&gt;http://localhost:4040&lt;/a&gt; shows every request hitting your tunnel, lets you replay, and is genuinely useful — if you're running a real server. It's not a hosted capture tool, but it's worth mentioning because half the time when someone says "webhook tester" they actually mean "let me see what ngrok caught."&lt;/p&gt;

&lt;p&gt;Best for: local development against a real handler.&lt;/p&gt;

&lt;p&gt;Watch out for: requires a running local server. Doesn't replace hosted capture for shared / CI use.&lt;/p&gt;

&lt;h1&gt;
  
  
  How to Pick
&lt;/h1&gt;

&lt;p&gt;Match the tool to the job:&lt;/p&gt;

&lt;p&gt;"I want to see what Stripe sends." Webhook.site or YoBox Webhook Tester.&lt;br&gt;
"I need to share a captured payload with a coworker." Webhook.site (the URL is the share link) or YoBox.&lt;br&gt;
"I want to script webhook capture from Cypress / Playwright." YoBox Webhook Tester — clean API, no auth, designed for CI.&lt;br&gt;
"I'm developing my own handler locally." ngrok inspector.&lt;br&gt;
"I want to mock an API my code calls." Beeceptor or Postman Mock Server.&lt;br&gt;
"I want to trigger workflows on incoming webhooks." RequestBin / Pipedream.&lt;br&gt;
"I want to self-host." Webhook.site (OSS) or Mockbin.&lt;/p&gt;

&lt;h1&gt;
  
  
  When You Want More Than Just Capture
&lt;/h1&gt;

&lt;p&gt;If you're testing real signup flows, capture alone isn't enough. Most flows fire an email AND a webhook — you need to assert on both. The YoBox Temp Mail + Webhook Tester pairing lets you:&lt;/p&gt;

&lt;p&gt;Generate a disposable email address.&lt;br&gt;
Generate a webhook capture URL.&lt;br&gt;
Configure your app to use both.&lt;br&gt;
Trigger the signup.&lt;br&gt;
Assert on the OTP email and the downstream webhook.&lt;br&gt;
Full pattern in "Cypress E2E with YoBox Disposable Email and Webhook Tester".&lt;/p&gt;

&lt;h1&gt;
  
  
  Pricing Honesty
&lt;/h1&gt;

&lt;p&gt;Most of these tools have generous free tiers. The pro tiers exist for teams that need:&lt;/p&gt;

&lt;p&gt;Persistent URLs that don't rotate&lt;br&gt;
SSO / SAML&lt;br&gt;
Compliance (SOC 2, GDPR)&lt;br&gt;
Long retention (30+ days)&lt;br&gt;
High request volume&lt;br&gt;
For individual developers, you almost never need pro. For startups, the team features start to matter around 5+ devs.&lt;/p&gt;

&lt;h1&gt;
  
  
  FAQ
&lt;/h1&gt;

&lt;p&gt;Is Webhook.site really safe for production debugging?&lt;br&gt;
For non-sensitive payloads, yes. Anything with PII, secrets, or financial data, you should self-host or use ephemeral URLs.&lt;/p&gt;

&lt;p&gt;Why use YoBox over Webhook.site?&lt;br&gt;
YoBox Webhook Tester is faster, has no ads or upsells, integrates with the rest of the YoBox toolbox (especially Temp Mail for end-to-end signup testing), and ships a clean JSON API designed for CI. Webhook.site is still excellent — they're complementary, and you should know both.&lt;/p&gt;

&lt;p&gt;Can I use any of these for production webhooks?&lt;br&gt;
No. These are for testing. Production webhooks should hit your own infrastructure with proper auth, retries, and persistence.&lt;/p&gt;

&lt;p&gt;Do these tools verify signatures?&lt;br&gt;
No — they capture the raw request, including the signature header. Verifying the signature is your handler's job.&lt;/p&gt;

&lt;p&gt;Which one has the best UI?&lt;br&gt;
Subjective, but YoBox and Webhook.site are both clean. Beeceptor's UI feels older. Mockbin's is dated.&lt;/p&gt;

&lt;h1&gt;
  
  
  Bottom Line
&lt;/h1&gt;

&lt;p&gt;Webhook.site isn't the only game in town anymore. For most developers in 2026, the right toolbox is: YoBox Webhook Tester for fast, free, scriptable capture; ngrok for local handler dev; Webhook.site as a backup when you need its specific templating features. Pick the one that fits the job, and stop reaching for "webhook.site" out of habit.&lt;/p&gt;

&lt;h1&gt;
  
  
  YoBox Team
&lt;/h1&gt;

&lt;p&gt;Builder behind YoBox — a privacy-first toolbox for developers and QA engineers covering disposable email, webhook capture, regex, secure passwords, Docker, and end-to-end testing.&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>webhooks</category>
      <category>nocode</category>
      <category>api</category>
    </item>
    <item>
      <title>Playwright Automation with YoBox: End-to-End Email &amp; Webhooks</title>
      <dc:creator>yobox</dc:creator>
      <pubDate>Tue, 18 Aug 2026 12:53:24 +0000</pubDate>
      <link>https://dev.to/yobox/playwright-automation-with-yobox-end-to-end-email-webhooks-io4</link>
      <guid>https://dev.to/yobox/playwright-automation-with-yobox-end-to-end-email-webhooks-io4</guid>
      <description>&lt;p&gt;Playwright handles the browser side of automation better than any tool that came before it. The other half — provisioning a real inbox per test, capturing a real webhook, doing both without ngrok and without a shared mailbox — is where teams burn weeks rebuilding the same plumbing. YoBox is that plumbing, pre-built, free, and reachable over plain HTTP.&lt;/p&gt;

&lt;p&gt;This guide is the practical companion to Playwright + YoBox. It focuses on the patterns that show up in production e2e suites: fixtures, retries, traces, and the rare-but-painful edge cases.&lt;/p&gt;

&lt;h1&gt;
  
  
  The fixture pattern
&lt;/h1&gt;

&lt;p&gt;Fixtures are the right place to put YoBox integration. They're scoped, typed, and auto-cleaned by Playwright.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// tests/fixtures.ts&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;test&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nx"&gt;base&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;@playwright/test&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;YOBOX&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;YOBOX&lt;/span&gt; &lt;span class="o"&gt;??&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;https://yobox.dev/api&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;test&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;base&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;extend&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="na"&gt;inbox&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nl"&gt;address&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;
&lt;span class="nl"&gt;hook&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nl"&gt;url&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
&lt;span class="na"&gt;inbox&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;({},&lt;/span&gt; &lt;span class="nx"&gt;use&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;r&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;$&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;YOBOX&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="sr"&gt;/mail/&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;method&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;POST&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="nf"&gt;use&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;r&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;
&lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="na"&gt;hook&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;({},&lt;/span&gt; &lt;span class="nx"&gt;use&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;r&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;$&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;YOBOX&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="sr"&gt;/hooks/&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;method&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;POST&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="nf"&gt;use&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;r&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;
&lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;expect&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;base&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Every test that lists inbox or hook in its args gets a fresh, isolated resource.&lt;/p&gt;

&lt;h1&gt;
  
  
  The waiter
&lt;/h1&gt;

&lt;p&gt;A 30-second poll with a 1.5-second interval covers 99% of email delivery scenarios.&lt;/p&gt;

&lt;p&gt;export async function waitForEmail(id: string, opts = { timeout: 30000, interval: 1500 }) {&lt;br&gt;
  const start = Date.now();&lt;br&gt;
  while (Date.now() - start &amp;lt; opts.timeout) {&lt;br&gt;
    const r = await fetch(&lt;code&gt;${process.env.YOBOX}/mail/${id}/messages&lt;/code&gt;);&lt;br&gt;
    const data = await r.json();&lt;br&gt;
    if (data.messages?.length) return data.messages[0];&lt;br&gt;
    await new Promise((r) =&amp;gt; setTimeout(r, opts.interval));&lt;br&gt;
  }&lt;br&gt;
  throw new Error(&lt;code&gt;Email timeout after ${opts.timeout}ms&lt;/code&gt;);&lt;br&gt;
}&lt;/p&gt;
&lt;h1&gt;
  
  
  Signup, OTP, redirect
&lt;/h1&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="nf"&gt;test&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;signup flow&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;inbox&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;goto&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/signup&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getByLabel&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Email&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;fill&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;inbox&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;address&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getByLabel&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Password&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;fill&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Sup3rSecret!2026&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getByRole&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;button&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Create account&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;}).&lt;/span&gt;&lt;span class="nf"&gt;click&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;msg&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;waitForEmail&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;inbox&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;otp&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;msg&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;text&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;match&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sr"&gt;/&lt;/span&gt;&lt;span class="se"&gt;\b\d{6}\b&lt;/span&gt;&lt;span class="sr"&gt;/&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;

&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getByLabel&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Code&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;fill&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;otp&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getByRole&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;button&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Verify&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;}).&lt;/span&gt;&lt;span class="nf"&gt;click&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;toHaveURL&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sr"&gt;/&lt;/span&gt;&lt;span class="se"&gt;\/&lt;/span&gt;&lt;span class="sr"&gt;welcome/&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;h1&gt;
  
  
  Password reset
&lt;/h1&gt;

&lt;p&gt;Same pattern, different email.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="nf"&gt;test&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;password reset&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;inbox&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="c1"&gt;// assume the user already exists with this inbox&lt;/span&gt;
&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;goto&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/forgot&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getByLabel&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Email&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;fill&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;inbox&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;address&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getByRole&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;button&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Send link&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;}).&lt;/span&gt;&lt;span class="nf"&gt;click&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;msg&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;waitForEmail&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;inbox&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;link&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;msg&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;text&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;match&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sr"&gt;/https&lt;/span&gt;&lt;span class="se"&gt;?&lt;/span&gt;&lt;span class="sr"&gt;:&lt;/span&gt;&lt;span class="se"&gt;\/\/[^\s&lt;/span&gt;&lt;span class="sr"&gt;)&lt;/span&gt;&lt;span class="se"&gt;]&lt;/span&gt;&lt;span class="sr"&gt;+/&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;

&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;goto&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;link&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getByLabel&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;New password&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;fill&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Resetted!42&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getByRole&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;button&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Update&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;}).&lt;/span&gt;&lt;span class="nf"&gt;click&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getByText&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Password updated&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)).&lt;/span&gt;&lt;span class="nf"&gt;toBeVisible&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h1&gt;
  
  
  Outbound webhooks
&lt;/h1&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="nf"&gt;test&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Stripe-style invoice.paid&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;hook&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;goto&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/admin/integrations&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getByLabel&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Webhook URL&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;fill&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;hook&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getByRole&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;button&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Save&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;}).&lt;/span&gt;&lt;span class="nf"&gt;click&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getByRole&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;button&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Send test invoice&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;}).&lt;/span&gt;&lt;span class="nf"&gt;click&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;req&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;waitForHook&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;hook&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nf"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;method&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;toBe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;POST&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;body&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;parse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nf"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;toBe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;invoice.paid&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nf"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;amount_cents&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;toBeGreaterThan&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h1&gt;
  
  
  Multi-context: two users, one test
&lt;/h1&gt;

&lt;p&gt;Playwright shines at multi-user scenarios. Pair two inboxes for invite flows:&lt;/p&gt;

&lt;p&gt;`&lt;code&gt;ts&lt;br&gt;
test("invite flow", async ({ browser }) =&amp;gt; {&lt;br&gt;
const [inviter, invitee] = await Promise.all([&lt;br&gt;
fetch(${process.env.YOBOX}/mail/new, { method: "POST" }).then(r =&amp;gt; r.json()),&lt;br&gt;
fetch(${process.env.YOBOX}/mail/new&lt;/code&gt;, { method: "POST" }).then(r =&amp;gt; r.json()),&lt;br&gt;
]);&lt;/p&gt;

&lt;p&gt;const ctxA = await browser.newContext();&lt;br&gt;
const pageA = await ctxA.newPage();&lt;br&gt;
await pageA.goto("/team/invite");&lt;br&gt;
await pageA.getByLabel("Email").fill(invitee.address);&lt;br&gt;
await pageA.getByRole("button", { name: "Send invite" }).click();&lt;/p&gt;

&lt;p&gt;const msg = await waitForEmail(invitee.id);&lt;br&gt;
const link = msg.text.match(/https?:\/\/[^\s)]+/)![0];&lt;/p&gt;

&lt;p&gt;const ctxB = await browser.newContext();&lt;br&gt;
const pageB = await ctxB.newPage();&lt;br&gt;
await pageB.goto(link);&lt;br&gt;
await expect(pageB.getByText("Welcome to the team")).toBeVisible();&lt;br&gt;
});&lt;br&gt;
`&lt;code&gt;&lt;/code&gt;typescript&lt;/p&gt;

&lt;h1&gt;
  
  
  Traces, retries, and debugging
&lt;/h1&gt;

&lt;p&gt;playwright.config.ts:&lt;/p&gt;

&lt;p&gt;export default defineConfig({&lt;br&gt;
  retries: process.env.CI ? 1 : 0,&lt;br&gt;
  use: { trace: "on-first-retry", video: "retain-on-failure" },&lt;br&gt;
});&lt;br&gt;
Attach the email body to failed runs so the trace viewer shows it:&lt;/p&gt;

&lt;p&gt;const msg = await waitForEmail(inbox.id);&lt;br&gt;
await testInfo.attach("inbox.txt", { body: msg.text, contentType: "text/plain" });&lt;/p&gt;

&lt;h1&gt;
  
  
  Comparison: per-test vs per-worker fixtures
&lt;/h1&gt;

&lt;p&gt;Scope   Pros    Cons&lt;br&gt;
test    Full isolation, simplest mental model   Slightly more HTTP calls&lt;br&gt;
worker  Faster for read-only resources  Cross-test leakage risk for inboxes&lt;br&gt;
Use test scope for inboxes and hooks. Always.&lt;/p&gt;

&lt;h1&gt;
  
  
  Pairs with
&lt;/h1&gt;

&lt;p&gt;Cypress + YoBox for teams running both runners.&lt;br&gt;
Password Generator for test user credentials.&lt;br&gt;
Regex Assistant for extraction patterns.&lt;br&gt;
Docker Builder for CI for containerized runs.&lt;/p&gt;

&lt;h1&gt;
  
  
  Common pitfalls
&lt;/h1&gt;

&lt;p&gt;page.waitForTimeout — replace with waitForEmail / waitForHook.&lt;br&gt;
Hard-coded OTPs in fixtures — extract every time, even in happy-path tests.&lt;br&gt;
Forgetting retries: 1 in CI — masks real bugs and causes false reds, in equal measure. One retry is the sweet spot.&lt;br&gt;
Mixing inbox scopes — keep them per-test.&lt;/p&gt;

&lt;h1&gt;
  
  
  FAQ
&lt;/h1&gt;

&lt;p&gt;Does this work with auth.setup.ts?&lt;br&gt;
Yes — set up the user once with a per-worker fixture, but still use a per-test inbox for fresh OTPs.&lt;/p&gt;

&lt;p&gt;Can I run cross-browser?&lt;br&gt;
Yes. YoBox is browser-agnostic; the same fixture serves Chromium, Firefox, and WebKit projects.&lt;/p&gt;

&lt;p&gt;What about mobile emulation?&lt;br&gt;
Same fixture, same waiter. The viewport doesn't matter.&lt;/p&gt;

&lt;p&gt;How do I assert email headers?&lt;br&gt;
The messages endpoint returns subject, from, to, and headers — assert directly on those.&lt;/p&gt;

&lt;h1&gt;
  
  
  Conclusion
&lt;/h1&gt;

&lt;p&gt;Playwright + YoBox is the closest the e2e world gets to free lunch. Two fixtures, one waiter, and you can honestly test signup, password reset, magic links, invites, and outbound webhooks — in parallel, across browsers, in CI, without ngrok and without a shared mailbox.&lt;/p&gt;

&lt;p&gt;See also: Playwright + YoBox guide, Cypress E2E with YoBox, Realistic Mock Data.&lt;/p&gt;

&lt;h1&gt;
  
  
  Advanced: API mocking + real email
&lt;/h1&gt;

&lt;p&gt;Playwright's \page.route\ lets you mock third-party APIs at the network layer while real emails still flow through YoBox. That hybrid is the sweet spot for testing flows that depend on a payment provider you don't want to ping.&lt;/p&gt;

&lt;p&gt;\\ts&lt;br&gt;
await page.route("/api.stripe.com/", (route) =&amp;gt;&lt;br&gt;
route.fulfill({ status: 200, body: JSON.stringify({ id: "pi_test" }) })&lt;br&gt;
);&lt;br&gt;
\\&lt;/p&gt;

&lt;h1&gt;
  
  
  Advanced: visual regression on email templates
&lt;/h1&gt;

&lt;p&gt;Render the YoBox HTML body inside a Playwright page, screenshot it, and snapshot-compare. Catches template regressions that text assertions miss.&lt;/p&gt;

&lt;h1&gt;
  
  
  Migration from Cypress
&lt;/h1&gt;

&lt;p&gt;The YoBox fixture pattern ports 1:1; the rest is syntax. Most teams migrate one folder per week and run both runners in CI during the transition.&lt;/p&gt;

&lt;h1&gt;
  
  
  Reporting
&lt;/h1&gt;

&lt;p&gt;Playwright HTML reports render attached artifacts inline — attach the email body and the webhook payload to every test so reports tell the full story without a debugger.&lt;/p&gt;

&lt;h1&gt;
  
  
  A production-grade Playwright setup
&lt;/h1&gt;

&lt;p&gt;The default playwright.config.ts ships with reasonable defaults, but a real project benefits from a few opinionated additions:&lt;/p&gt;

&lt;p&gt;// playwright.config.ts&lt;br&gt;
import { defineConfig, devices } from "@playwright/test";&lt;br&gt;
export default defineConfig({&lt;br&gt;
  testDir: "./tests",&lt;br&gt;
  fullyParallel: true,&lt;br&gt;
  forbidOnly: !!process.env.CI,&lt;br&gt;
  retries: process.env.CI ? 2 : 0,&lt;br&gt;
  reporter: [["html"], ["junit", { outputFile: "junit.xml" }]],&lt;br&gt;
  use: {&lt;br&gt;
    baseURL: process.env.BASE_URL ?? "&lt;a href="http://localhost:3000" rel="noopener noreferrer"&gt;http://localhost:3000&lt;/a&gt;",&lt;br&gt;
    trace: "on-first-retry",&lt;br&gt;
    screenshot: "only-on-failure",&lt;br&gt;
    video: "retain-on-failure",&lt;br&gt;
  },&lt;br&gt;
  projects: [&lt;br&gt;
    { name: "chromium", use: devices["Desktop Chrome"] },&lt;br&gt;
    { name: "webkit",   use: devices["Desktop Safari"] },&lt;br&gt;
    { name: "firefox",  use: devices["Desktop Firefox"] },&lt;br&gt;
    { name: "mobile",   use: devices["Pixel 7"] },&lt;br&gt;
  ],&lt;br&gt;
});&lt;/p&gt;

&lt;h1&gt;
  
  
  A reusable YoBox fixture
&lt;/h1&gt;

&lt;p&gt;Fixtures are how Playwright keeps tests readable. Wrap inbox creation and webhook URL generation so every test starts with the right primitives.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;&lt;/code&gt;`ts&lt;br&gt;
// tests/fixtures/yobox.ts&lt;br&gt;
import { test as base, expect } from "@playwright/test";&lt;br&gt;
import { request } from "node:https";&lt;/p&gt;

&lt;p&gt;type Yobox = {&lt;br&gt;
newInbox(): Promise&amp;lt;{ address: string; pollOtp(): Promise }&amp;gt;;&lt;br&gt;
newHook(): Promise&amp;lt;{ url: string; waitFor(method?: string): Promise }&amp;gt;;&lt;br&gt;
};&lt;/p&gt;

&lt;p&gt;export const test = base.extend&amp;lt;{ yobox: Yobox }&amp;gt;({&lt;br&gt;
yobox: async ({}, use) =&amp;gt; {&lt;br&gt;
await use({&lt;br&gt;
async newInbox() {&lt;br&gt;
const r = await fetch("&lt;a href="https://yobox.dev/api/mail/new" rel="noopener noreferrer"&gt;https://yobox.dev/api/mail/new&lt;/a&gt;", { method: "POST" });&lt;br&gt;
const { address, token } = await r.json();&lt;br&gt;
return {&lt;br&gt;
address,&lt;br&gt;
async pollOtp() {&lt;br&gt;
const deadline = Date.now() + 30_000;&lt;br&gt;
while (Date.now() &amp;lt; deadline) {&lt;br&gt;
const m = await fetch(&lt;a href="https://yobox.dev/api/mail/$%7Btoken%7D/latest).then(r" rel="noopener noreferrer"&gt;https://yobox.dev/api/mail/${token}/latest).then(r&lt;/a&gt; =&amp;gt; r.json());&lt;br&gt;
const code = m?.text?.match(/\b\d{6}\b/)?.[0];&lt;br&gt;
if (code) return code;&lt;br&gt;
await new Promise(r =&amp;gt; setTimeout(r, 1000));&lt;br&gt;
}&lt;br&gt;
throw new Error("OTP timeout");&lt;br&gt;
},&lt;br&gt;
};&lt;br&gt;
},&lt;br&gt;
async newHook() {&lt;br&gt;
const id = crypto.randomUUID();&lt;br&gt;
const url = &lt;a href="https://yobox.dev/api/hooks/$%7Bid%7D" rel="noopener noreferrer"&gt;https://yobox.dev/api/hooks/${id}&lt;/a&gt;;&lt;br&gt;
return {&lt;br&gt;
url,&lt;br&gt;
async waitFor(method = "POST") {&lt;br&gt;
const deadline = Date.now() + 15_000;&lt;br&gt;
while (Date.now() &amp;lt; deadline) {&lt;br&gt;
const r = await fetch(&lt;a href="https://yobox.dev/api/hooks/$%7Bid%7D).then(r" rel="noopener noreferrer"&gt;https://yobox.dev/api/hooks/${id}).then(r&lt;/a&gt; =&amp;gt; r.json());&lt;br&gt;
const hit = r.requests?.find((x: any) =&amp;gt; x.method === method);&lt;br&gt;
if (hit) return hit;&lt;br&gt;
await new Promise(r =&amp;gt; setTimeout(r, 500));&lt;br&gt;
}&lt;br&gt;
throw new Error("Webhook timeout");&lt;br&gt;
},&lt;br&gt;
};&lt;br&gt;
},&lt;br&gt;
});&lt;br&gt;
},&lt;br&gt;
});&lt;br&gt;
export { expect };&lt;br&gt;
`&lt;code&gt;&lt;/code&gt;&lt;/p&gt;

&lt;h1&gt;
  
  
  End-to-end signup test
&lt;/h1&gt;

&lt;p&gt;&lt;code&gt;&lt;/code&gt;`ts&lt;br&gt;
import { test, expect } from "./fixtures/yobox";&lt;/p&gt;

&lt;p&gt;test("signup with OTP", async ({ page, yobox }) =&amp;gt; {&lt;br&gt;
const inbox = await yobox.newInbox();&lt;br&gt;
await page.goto("/signup");&lt;br&gt;
await page.getByLabel("Email").fill(inbox.address);&lt;br&gt;
await page.getByRole("button", { name: "Send code" }).click();&lt;br&gt;
const code = await inbox.pollOtp();&lt;br&gt;
await page.getByLabel("Verification code").fill(code);&lt;br&gt;
await page.getByRole("button", { name: "Continue" }).click();&lt;br&gt;
await expect(page.getByRole("heading", { name: /welcome/i })).toBeVisible();&lt;br&gt;
});&lt;br&gt;
`&lt;code&gt;&lt;/code&gt;&lt;/p&gt;

&lt;h1&gt;
  
  
  Async webhook test
&lt;/h1&gt;

&lt;p&gt;&lt;code&gt;&lt;/code&gt;`ts&lt;br&gt;
test("integration emits webhook", async ({ page, yobox }) =&amp;gt; {&lt;br&gt;
const hook = await yobox.newHook();&lt;br&gt;
await page.goto("/integrations/new");&lt;br&gt;
await page.getByLabel("Webhook URL").fill(hook.url);&lt;br&gt;
await page.getByRole("button", { name: "Save" }).click();&lt;/p&gt;

&lt;p&gt;await page.getByRole("button", { name: "Send test event" }).click();&lt;br&gt;
const received = await hook.waitFor("POST");&lt;br&gt;
expect(received.headers["content-type"]).toContain("application/json");&lt;br&gt;
expect(JSON.parse(received.body).event).toBe("integration.test");&lt;br&gt;
});&lt;br&gt;
`&lt;code&gt;&lt;/code&gt;&lt;/p&gt;

&lt;h1&gt;
  
  
  CI/CD with Playwright
&lt;/h1&gt;

&lt;h1&gt;
  
  
  .github/workflows/e2e.yml
&lt;/h1&gt;

&lt;p&gt;jobs:&lt;br&gt;
  e2e:&lt;br&gt;
    runs-on: ubuntu-latest&lt;br&gt;
    steps:&lt;br&gt;
      - uses: actions/checkout@v4&lt;br&gt;
      - uses: actions/setup-node@v4&lt;br&gt;
        with: { node-version: 20 }&lt;br&gt;
      - run: npm ci&lt;br&gt;
      - run: npx playwright install --with-deps&lt;br&gt;
      - run: npx playwright test&lt;br&gt;
      - if: failure()&lt;br&gt;
        uses: actions/upload-artifact@v4&lt;br&gt;
        with:&lt;br&gt;
          name: playwright-report&lt;br&gt;
          path: playwright-report/&lt;br&gt;
For containerized runs, see the Docker builder pattern for Cypress and Playwright CI.&lt;/p&gt;

&lt;h1&gt;
  
  
  Playwright vs. Cypress for YoBox workflows
&lt;/h1&gt;

&lt;p&gt;Concern Playwright  Cypress&lt;br&gt;
Multi-tab / multi-origin    First class Workarounds&lt;br&gt;
Network interception    Both ways   Both ways&lt;br&gt;
Parallel execution  Built-in    Dashboard / sharding&lt;br&gt;
Polling YoBox inbox in-test Native fetch    cy.task&lt;br&gt;
Mobile emulation    Built-in    Viewport-only&lt;br&gt;
Pick Playwright when you need multi-context (signup in one tab, admin approval in another). Pick Cypress when your team already lives in the Cypress runner.&lt;/p&gt;

&lt;h1&gt;
  
  
  Troubleshooting
&lt;/h1&gt;

&lt;p&gt;OTP times out.&lt;br&gt;
Provider delivery delay. Increase the deadline to 60s, and verify your sending service isn't throttling YoBox addresses.&lt;/p&gt;

&lt;p&gt;Webhook never arrives.&lt;br&gt;
The most common bug: the URL written to the form has a typo because of HTML autofill. Use page.getByLabel(...).fill(value) and re-read the field with inputValue() to confirm.&lt;/p&gt;

&lt;p&gt;Tests flake under parallelism.&lt;br&gt;
Make sure each test creates its own inbox and webhook. Shared state across workers is the single most common flake source.&lt;/p&gt;

&lt;h1&gt;
  
  
  FAQ
&lt;/h1&gt;

&lt;p&gt;Can I run Playwright against production?&lt;br&gt;
You can — but use ephemeral YoBox inboxes and webhook URLs so test artifacts don't litter your real systems.&lt;/p&gt;

&lt;p&gt;How do I debug a single test?&lt;br&gt;
npx playwright test --debug path/to/test.spec.ts. The Inspector lets you step through and modify selectors live.&lt;/p&gt;

&lt;p&gt;Does YoBox have a rate limit?&lt;br&gt;
The public endpoints are designed for human and CI-scale use. If you push beyond that, reach out — happy to whitelist sensible workloads.&lt;/p&gt;

&lt;h1&gt;
  
  
  YoBox Team
&lt;/h1&gt;

&lt;p&gt;Builder behind YoBox — a privacy-first toolbox for developers and QA engineers covering disposable email, webhook capture, regex, secure passwords, Docker, and end-to-end testing.&lt;/p&gt;

</description>
      <category>testing</category>
      <category>automation</category>
      <category>webdev</category>
      <category>javascript</category>
    </item>
    <item>
      <title>Stop Using Fake Data in Production Demos</title>
      <dc:creator>yobox</dc:creator>
      <pubDate>Sun, 16 Aug 2026 15:19:16 +0000</pubDate>
      <link>https://dev.to/yobox/stop-using-fake-data-in-production-demos-42of</link>
      <guid>https://dev.to/yobox/stop-using-fake-data-in-production-demos-42of</guid>
      <description>&lt;p&gt;Investors, customers, and even your own teammates struggle to evaluate a product when the demo data reads like a unit test fixture.&lt;/p&gt;

&lt;h1&gt;
  
  
  Quick rules
&lt;/h1&gt;

&lt;p&gt;Names from real cultures (mix locales).&lt;br&gt;
Emails on a realistic domain (&lt;a class="mentioned-user" href="https://dev.to/acme"&gt;@acme&lt;/a&gt;.com, not &lt;a class="mentioned-user" href="https://dev.to/test"&gt;@test&lt;/a&gt;).&lt;br&gt;
Currency amounts that look like a real business (\$1,247.50, not \$100).&lt;br&gt;
Timestamps relative to today, not 1970.&lt;br&gt;
YoBox's Mock Data generator outputs CSV or JSON with these defaults baked in.&lt;/p&gt;

&lt;h1&gt;
  
  
  Why "foo / bar / baz" is killing your demos
&lt;/h1&gt;

&lt;p&gt;Stakeholders evaluate products with their eyes before they evaluate them with their heads. A polished feature populated with &lt;a href="mailto:test1@test.com"&gt;test1@test.com&lt;/a&gt;, Lorem ipsum, and $100.00 looks unfinished, even when the underlying logic is flawless. The signal you are sending — "I did not care enough to make this look real" — overrides whatever you are trying to demonstrate.&lt;/p&gt;

&lt;p&gt;This is not a UX nitpick. It is a sales and fundraising problem. Investors pattern-match on polish. Enterprise buyers pattern-match on credibility. Internal stakeholders pattern-match on "is this team paying attention to details?" Fake-looking data answers all three questions with "no."&lt;/p&gt;

&lt;p&gt;Your demo data is part of your product. Treat it that way.&lt;/p&gt;

&lt;h1&gt;
  
  
  What "realistic" actually means
&lt;/h1&gt;

&lt;p&gt;Realistic data is not random data. It has structure that matches a believable business:&lt;/p&gt;

&lt;p&gt;Name distributions that reflect real demographics, not a single Anglo locale.&lt;br&gt;
Email domains that look like real companies (@northwindsolutions.com), not @example.com.&lt;br&gt;
Currency with believable cents and ranges ($1,247.83, not $100.00 repeated forty times).&lt;br&gt;
Timestamps clustered around today, with realistic gaps (more activity during business hours).&lt;br&gt;
Geographic spread that matches your customer story — not 100% San Francisco.&lt;br&gt;
Status distributions that show variety — mostly "active" with a few "pending" and "churned".&lt;br&gt;
The YoBox Mock Data generator bakes these defaults in. You get CSV or JSON output that already passes the "does this look like a real Salesforce export?" test.&lt;/p&gt;

&lt;h1&gt;
  
  
  The cost of unrealistic data, in dollars
&lt;/h1&gt;

&lt;p&gt;A realistic example, drawn from teams we have worked with:&lt;/p&gt;

&lt;p&gt;Stage   Cost of bad demo data&lt;br&gt;
Sales demo  Deal stalls — buyer asks "is this product even used by real customers?"&lt;br&gt;
Investor pitch  Slower second-meeting conversion&lt;br&gt;
User testing    Participants comment on the data instead of the feature&lt;br&gt;
Internal review Execs nitpick formatting instead of evaluating the design&lt;br&gt;
Marketing screenshots   Need to be redone before launch, often the day before&lt;br&gt;
None of these costs are theoretical. Every one of them lands in the calendar of someone senior, who then asks why the demo data is bad. That is not a conversation any IC wants to be in.&lt;/p&gt;

&lt;h1&gt;
  
  
  A workflow for generating demo data once and reusing it
&lt;/h1&gt;

&lt;p&gt;The mistake is generating demo data ad hoc inside each tool. The fix is producing a canonical dataset and pointing every environment at it.&lt;/p&gt;

&lt;p&gt;Decide on a fictional company universe — pick 8–12 company names, 50–100 contacts, 5–10 product SKUs.&lt;br&gt;
Generate the rows once with YoBox Mock Data (or Faker on the backend).&lt;br&gt;
Commit the dataset as a JSON or CSV file in your repo under fixtures/demo/.&lt;br&gt;
Seed it into staging on every deploy.&lt;br&gt;
Reference the same characters in product screenshots, docs, and pitch decks.&lt;br&gt;
Consistency is what makes the data feel real. When the same "Northwind Solutions" appears in a screenshot, a demo video, and the in-product example modal, viewers start trusting the artifact.&lt;/p&gt;

&lt;h1&gt;
  
  
  Realistic patterns by domain
&lt;/h1&gt;

&lt;p&gt;B2B SaaS&lt;br&gt;
Use company names that hint at industry (Northwind Logistics, Aperture Analytics). Mix enterprise and SMB plan tiers. Include at least one trial account and one churned account so the dashboard does not look monotonously green.&lt;/p&gt;

&lt;p&gt;Fintech&lt;br&gt;
Currency variety matters. Mix small recurring charges ($9.99), mid-range transactions ($247.50), and the occasional outlier ($12,480.00). Real ledgers are heavy-tailed.&lt;/p&gt;

&lt;p&gt;Healthcare and HR&lt;br&gt;
Use locale-appropriate names. A US-based product showing 100% John Smith looks careless; a global product showing 100% Anglo names looks worse.&lt;/p&gt;

&lt;p&gt;Consumer&lt;br&gt;
Realistic timestamps are everything. Activity spikes during evenings and weekends. Engagement falls off a cliff for inactive cohorts. A flat usage chart screams "fake."&lt;/p&gt;

&lt;h1&gt;
  
  
  Tooling comparison
&lt;/h1&gt;

&lt;p&gt;Tool    Use case    Output  Where it shines&lt;br&gt;
YoBox Mock Data Quick CSV/JSON for demos and tests  CSV, JSON   Frontend-only, instant, sensible defaults&lt;br&gt;
Faker.js    Programmatic generation in code Anything    Custom logic, fine control&lt;br&gt;
Mockaroo    Browser-based heavy customization   CSV, SQL, JSON  Complex schemas&lt;br&gt;
Snaplet Production-cloning for dev DBs  Real DB clone   Late-stage staging&lt;br&gt;
For most demo and test scenarios, the YoBox generator is the fastest path to "looks real enough to ship a screenshot."&lt;/p&gt;

&lt;h1&gt;
  
  
  Pair with disposable email for end-to-end realism
&lt;/h1&gt;

&lt;p&gt;When demoing a signup flow, generate the user with the Mock Data generator and verify the OTP in Temp Mail. Watching a real OTP land in a real inbox during a live demo is one of the most credibility-boosting moments you can engineer.&lt;/p&gt;

&lt;h1&gt;
  
  
  Key takeaways
&lt;/h1&gt;

&lt;p&gt;Demo data is part of the product. Polish it.&lt;br&gt;
Realistic data has structure — varied names, locales, amounts, timestamps.&lt;br&gt;
Generate a canonical dataset once and reuse it across staging, screenshots, and decks.&lt;br&gt;
Use the YoBox Mock Data generator for instant, sensible defaults.&lt;br&gt;
Pair with Temp Mail and the Webhook Tester for end-to-end demo flows.&lt;/p&gt;

&lt;h1&gt;
  
  
  Real use cases
&lt;/h1&gt;

&lt;p&gt;Pitch deck screenshots&lt;br&gt;
Generate the dataset Monday. Screenshot Tuesday. Ship Wednesday. The same characters appear in every slide.&lt;/p&gt;

&lt;p&gt;Sales demo environments&lt;br&gt;
Reset the staging DB nightly from the fixture file. Every demo starts from the same believable state.&lt;/p&gt;

&lt;p&gt;User research&lt;br&gt;
Participants stop saying "this isn't realistic" and start commenting on the feature you actually wanted feedback on.&lt;/p&gt;

&lt;p&gt;Onboarding tours&lt;br&gt;
Tour data that looks real teaches users what good usage looks like. Tour data full of foo teaches them you do not test your own onboarding.&lt;/p&gt;

&lt;h1&gt;
  
  
  FAQ
&lt;/h1&gt;

&lt;p&gt;Is generated data safe for production demos?&lt;br&gt;
Yes, as long as it is clearly fictional and does not impersonate real companies or individuals.&lt;/p&gt;

&lt;p&gt;How much demo data is enough?&lt;br&gt;
Enough that scroll states feel populated. For a list view, 30–50 rows. For a dashboard, 90 days of activity.&lt;/p&gt;

&lt;p&gt;Should I localize?&lt;br&gt;
If your product serves multiple regions, yes. A single-locale dataset undersells your global story.&lt;/p&gt;

&lt;p&gt;Can I use real customer data, anonymized?&lt;br&gt;
Only with extreme care, written policy, and legal review. Generated data avoids the entire category of risk.&lt;/p&gt;

&lt;p&gt;Where does YoBox generate the data?&lt;br&gt;
In the browser. Nothing is sent to a server. See the Mock Data tool.&lt;/p&gt;

&lt;h1&gt;
  
  
  Building a reusable "demo persona" library
&lt;/h1&gt;

&lt;p&gt;The single highest-leverage move a growth or product team can make is to invest one afternoon in a reusable demo persona library. Generate it once with the YoBox Mock Data generator, commit it to your repo, and reference the same characters everywhere — in screenshots, in onboarding tours, in case studies, in fixture data for Cypress and Playwright tests.&lt;/p&gt;

&lt;p&gt;A useful persona library has three layers:&lt;/p&gt;

&lt;h2&gt;
  
  
  Companies — 8–12 fictional organizations with names, industries, sizes, and plan tiers.
&lt;/h2&gt;

&lt;h2&gt;
  
  
  People — 50–100 contacts mapped to those companies, with realistic name distributions and job titles.
&lt;/h2&gt;

&lt;h2&gt;
  
  
  Activity — timestamps, transactions, and engagement events stretched over the last 90 days.
&lt;/h2&gt;

&lt;p&gt;When every artifact reuses the same personas, your product develops a quiet sense of continuity. Viewers stop noticing the data and start noticing the product.&lt;/p&gt;

&lt;p&gt;Updating the library&lt;br&gt;
Refresh the activity timestamps weekly so dashboards never show suspiciously stale data. The companies and people can stay stable for months — that consistency is the point.&lt;/p&gt;

&lt;p&gt;Sharing across teams&lt;br&gt;
Store the fixture in a public-ish location (an internal Notion page, a shared Drive folder, or a fixtures/ directory in your monorepo) so design, marketing, and engineering all draw from the same source. The minute one team starts inventing its own demo names is the minute the polish starts to fracture.&lt;/p&gt;

&lt;p&gt;Demo data anti-patterns to retire today&lt;br&gt;
A short list of things that quietly damage credibility every time they appear in a demo: emails on &lt;a class="mentioned-user" href="https://dev.to/test"&gt;@test&lt;/a&gt;.com or @example.org; sequential IDs like User 1, User 2, User 3 shown to end users; timestamps that all share the same second; currency values that all end in .00; status fields where every row says "active"; and avatar placeholders that look like broken images. Each one is a five-minute fix and a 5% credibility upgrade. Combined, they are the difference between a demo that lands and a demo that drifts. The YoBox Mock Data generator avoids all of them by default, which is the entire point.&lt;/p&gt;

&lt;h1&gt;
  
  
  Conclusion
&lt;/h1&gt;

&lt;p&gt;The fastest credibility upgrade most products can ship is better demo data. It costs nothing, takes an afternoon, and changes how every future stakeholder reacts to the product. Stop typing foo. Open the YoBox Mock Data generator, commit the output, and let the data carry its share of the demo.&lt;/p&gt;

&lt;h1&gt;
  
  
  YoBox Team
&lt;/h1&gt;

&lt;p&gt;Builder behind YoBox — a privacy-first toolbox for developers and QA engineers covering disposable email, webhook capture, regex, secure passwords, Docker, and end-to-end testing.&lt;/p&gt;

</description>
      <category>softwaredevelopment</category>
      <category>webdev</category>
      <category>programming</category>
      <category>data</category>
    </item>
    <item>
      <title>How to Set Up a Webhook Receiver Without Running a Server</title>
      <dc:creator>yobox</dc:creator>
      <pubDate>Fri, 14 Aug 2026 14:49:16 +0000</pubDate>
      <link>https://dev.to/yobox/how-to-set-up-a-webhook-receiver-without-running-a-server-4294</link>
      <guid>https://dev.to/yobox/how-to-set-up-a-webhook-receiver-without-running-a-server-4294</guid>
      <description>&lt;p&gt;Webhooks need somewhere to land. Traditionally that meant standing up a web server, opening a port, configuring HTTPS, handling retries, persisting payloads, and praying nothing crashes when traffic spikes.&lt;/p&gt;

&lt;p&gt;In 2026, none of that is necessary for most use cases. There are at least four good ways to receive webhooks without running infrastructure of your own. This guide covers each, with honest verdicts on when to use which.&lt;/p&gt;

&lt;h1&gt;
  
  
  The Four Options
&lt;/h1&gt;

&lt;p&gt;Capture services — for inspection, debugging, and quick automation&lt;br&gt;
Serverless functions — for production logic without infrastructure&lt;br&gt;
Automation platforms — for visual workflows without code&lt;br&gt;
Lightweight self-hosting on managed runtimes — for control without ops&lt;br&gt;
Pick by use case.&lt;/p&gt;

&lt;h1&gt;
  
  
  Option 1: Capture Services
&lt;/h1&gt;

&lt;p&gt;Tools like the YoBox Webhook Tester, Webhook.site, and Pipedream's RequestBin give you a unique URL that captures any POST it receives and shows you the headers, query, and body in a browser. No server, no code, no signup (in most cases).&lt;/p&gt;

&lt;p&gt;Use for:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Inspection. What is Stripe actually sending?&lt;/li&gt;
&lt;li&gt;Debugging. Why isn't my handler responding the way I expect?&lt;/li&gt;
&lt;li&gt;Sharing. Send a coworker a captured payload.&lt;/li&gt;
&lt;li&gt;Lightweight testing. Assert in CI that your app sent the right webhook.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Don't use for:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Production. Captured webhooks aren't durable, retried, or processed.&lt;/li&gt;
&lt;li&gt;Sensitive payloads. Public capture services are not encrypted.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Tool to start with: YoBox Webhook Tester for a clean, no-signup, API-first experience.&lt;/p&gt;

&lt;h1&gt;
  
  
  Option 2: Serverless Functions
&lt;/h1&gt;

&lt;p&gt;Vercel, Netlify, Cloudflare Workers, AWS Lambda + API Gateway, and similar platforms let you write a small function that runs on demand when an HTTPS request hits a URL they give you.&lt;/p&gt;

&lt;p&gt;A Vercel example:&lt;/p&gt;

&lt;p&gt;// app/api/webhook/route.ts&lt;br&gt;
export async function POST(req: Request) {&lt;br&gt;
  const signature = req.headers.get('stripe-signature')!;&lt;br&gt;
  const body = await req.text();&lt;br&gt;
  // verify signature, do work, return 200&lt;br&gt;
  return new Response('ok', { status: 200 });&lt;br&gt;
}&lt;br&gt;
A Cloudflare Workers example:&lt;/p&gt;

&lt;p&gt;export default {&lt;br&gt;
  async fetch(req: Request, env: Env) {&lt;br&gt;
    if (req.method !== 'POST') return new Response('method not allowed', { status: 405 });&lt;br&gt;
    const body = await req.text();&lt;br&gt;
    await env.QUEUE.send({ body });&lt;br&gt;
    return new Response('ok');&lt;br&gt;
  },&lt;br&gt;
};&lt;br&gt;
Use for:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Production webhook handlers that do real work&lt;/li&gt;
&lt;li&gt;Anything where you control the code and want to keep doing that&lt;/li&gt;
&lt;li&gt;Cases where you need to verify signatures, persist data, fire downstream API calls&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Don't use for:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Long-running work (most platforms cap at 10-60 seconds). Use a queue.&lt;/li&gt;
&lt;li&gt;Stateful protocols. Serverless is per-request.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Cost: typically free for low volume; cents per million invocations at scale.&lt;/p&gt;

&lt;h1&gt;
  
  
  Option 3: Automation Platforms
&lt;/h1&gt;

&lt;p&gt;Zapier, Pipedream, Make, n8n, and similar platforms expose a "webhook trigger" that starts a visual workflow on incoming POST. You build the rest of the workflow in their UI — Slack notifications, sheet updates, API calls.&lt;/p&gt;

&lt;p&gt;Use for:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Connecting webhook events to other SaaS without code&lt;/li&gt;
&lt;li&gt;Slack/Discord notifications on inbound events&lt;/li&gt;
&lt;li&gt;Quick fan-out: webhook → 5 different actions&lt;/li&gt;
&lt;li&gt;Citizen-developer use cases&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Don't use for:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Complex logic. Visual workflows get unwieldy fast.&lt;/li&gt;
&lt;li&gt;High throughput. Platform pricing scales with operations.&lt;/li&gt;
&lt;li&gt;Strict latency. Visual platforms have measurable overhead.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Cost: free tiers exist; paid tiers start around $20-50/mo for serious use.&lt;/p&gt;

&lt;h1&gt;
  
  
  Option 4: Lightweight Self-Hosting
&lt;/h1&gt;

&lt;p&gt;Managed runtimes like Render, Fly.io, Railway, and Cloudflare Workers (with Durable Objects) let you deploy a tiny server with one command. It's "self-hosted" in the sense that you wrote the code, but you're not managing infrastructure.&lt;/p&gt;

&lt;p&gt;// A minimal Bun server, deployable to Render in one click&lt;br&gt;
Bun.serve({&lt;br&gt;
  port: process.env.PORT || 3000,&lt;br&gt;
  async fetch(req) {&lt;br&gt;
    if (new URL(req.url).pathname === '/webhook' &amp;amp;&amp;amp; req.method === 'POST') {&lt;br&gt;
      const body = await req.text();&lt;br&gt;
      // ... handle&lt;br&gt;
      return new Response('ok');&lt;br&gt;
    }&lt;br&gt;
    return new Response('not found', { status: 404 });&lt;br&gt;
  },&lt;br&gt;
});&lt;br&gt;
Use for:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Webhook handlers that need persistence (own database)&lt;/li&gt;
&lt;li&gt;Multi-route services (you have other endpoints too)&lt;/li&gt;
&lt;li&gt;Production at moderate scale where serverless cold starts hurt&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Don't use for:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Sub-second cold-start sensitive workloads (use Cloudflare Workers instead)&lt;/li&gt;
&lt;li&gt;True zero-ops (you still own the codebase)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Cost: Render/Fly/Railway start at $5-10/mo for a small instance.&lt;/p&gt;

&lt;h1&gt;
  
  
  Decision Matrix
&lt;/h1&gt;

&lt;p&gt;Need    Best fit&lt;br&gt;
Just inspect what Stripe sends  YoBox Webhook Tester&lt;br&gt;
Production webhook handler with custom logic    Vercel / Cloudflare Workers&lt;br&gt;
"When this webhook fires, post to Slack"    Zapier / Pipedream&lt;br&gt;
Multi-route API + webhooks  Render / Fly.io&lt;br&gt;
CI tests that assert on webhook delivery    YoBox Webhook Tester API&lt;br&gt;
Webhook that triggers a background job  Serverless + queue (SQS, Cloudflare Queues)&lt;/p&gt;

&lt;h1&gt;
  
  
  Patterns for Each Option
&lt;/h1&gt;

&lt;p&gt;Capture-first development&lt;br&gt;
Use the YoBox Webhook Tester to see what the provider sends, save the payload as a fixture, then write your serverless handler against the fixture. You'll be production-ready in a quarter the time.&lt;/p&gt;

&lt;p&gt;Serverless + queue&lt;br&gt;
For any non-trivial work, the serverless function should return 2xx immediately and push the payload to a queue. A separate consumer does the actual work. This pattern survives traffic spikes and retries gracefully.&lt;/p&gt;

&lt;p&gt;Hybrid: serverless prod, capture in dev&lt;br&gt;
In production, your serverless handler is the canonical receiver. In development, you have a second URL (the YoBox Webhook Tester) that mirrors the same payloads, so you can debug without affecting prod state.&lt;/p&gt;

&lt;p&gt;Pair with disposable email&lt;br&gt;
If your webhook handler is part of a signup flow, pair the webhook capture with YoBox Temp Mail for full async coverage. See "Email Testing Guide for Developers".&lt;/p&gt;

&lt;h1&gt;
  
  
  Common Pitfalls
&lt;/h1&gt;

&lt;p&gt;Forgetting to return 2xx quickly&lt;br&gt;
Most providers retry on 5xx for hours. If your handler takes 30 seconds to respond, the provider will retry and you'll process the same event multiple times.&lt;/p&gt;

&lt;p&gt;Skipping signature verification because "it's just a test"&lt;br&gt;
You'll deploy that code. Always verify, even in dev.&lt;/p&gt;

&lt;p&gt;Letting cold starts cause provider retries&lt;br&gt;
If your serverless function cold-starts in 5 seconds and the provider times out at 10, you're at the edge of safety. Consider Cloudflare Workers for sub-100ms cold starts.&lt;/p&gt;

&lt;p&gt;Trying to do everything in one function&lt;br&gt;
Webhook handler + business logic + database writes + downstream API calls + email sending all in one function = brittle. Split: handler returns 2xx fast, queue takes the load.&lt;/p&gt;

&lt;p&gt;Hardcoding capture URLs in production&lt;br&gt;
A capture URL is for debugging. Don't ship it to production by accident.&lt;/p&gt;

&lt;h1&gt;
  
  
  When You Should Run a Real Server
&lt;/h1&gt;

&lt;p&gt;There are still good reasons to run a real server:&lt;/p&gt;

&lt;p&gt;Long-lived connections. WebSockets, SSE, gRPC streams.&lt;br&gt;
Stateful protocols. Sessions, sticky routing.&lt;br&gt;
High-throughput / low-latency where serverless cold starts hurt.&lt;br&gt;
Compliance regimes that require dedicated infrastructure.&lt;br&gt;
For everything else in 2026, you don't need a server.&lt;/p&gt;

&lt;h1&gt;
  
  
  FAQ
&lt;/h1&gt;

&lt;p&gt;What's the easiest way to receive a webhook?&lt;br&gt;
For inspection: YoBox Webhook Tester. For real handling: a Vercel or Cloudflare Workers function.&lt;/p&gt;

&lt;p&gt;Do I need HTTPS?&lt;br&gt;
Yes. Most providers refuse to POST to HTTP URLs.&lt;/p&gt;

&lt;p&gt;How do I handle retries?&lt;br&gt;
Make your handler idempotent (check the event ID, no-op if already processed). The provider will retry on 5xx — your job is to make duplicates safe.&lt;/p&gt;

&lt;p&gt;Can I receive webhooks on localhost?&lt;br&gt;
Not directly. Use a tunnel (ngrok, localtunnel) or capture in YoBox and replay locally.&lt;/p&gt;

&lt;p&gt;Is serverless really cheap enough?&lt;br&gt;
For most webhook workloads, yes. 1M invocations on Cloudflare Workers is $0.30. Vercel's free tier covers ~100K/mo.&lt;/p&gt;

&lt;h1&gt;
  
  
  Bottom Line
&lt;/h1&gt;

&lt;p&gt;You don't need a server to receive a webhook in 2026. Use YoBox Webhook Tester for inspection, a serverless function for production, an automation platform for no-code fan-out, or a managed runtime if you want more control. Pick by use case, not by habit, and stop standing up VPSes for things that fit in 20 lines of code.&lt;/p&gt;

&lt;h1&gt;
  
  
  YoBox Team
&lt;/h1&gt;

&lt;p&gt;Builder behind YoBox — a privacy-first toolbox for developers and QA engineers covering disposable email, webhook capture, regex, secure passwords, Docker, and end-to-end testing.&lt;/p&gt;

</description>
      <category>webhooks</category>
      <category>serverless</category>
      <category>api</category>
      <category>backend</category>
    </item>
    <item>
      <title>Why OTP Verification Fails (and How to Fix It)</title>
      <dc:creator>yobox</dc:creator>
      <pubDate>Wed, 12 Aug 2026 16:19:01 +0000</pubDate>
      <link>https://dev.to/yobox/why-otp-verification-fails-and-how-to-fix-it-2d0a</link>
      <guid>https://dev.to/yobox/why-otp-verification-fails-and-how-to-fix-it-2d0a</guid>
      <description>&lt;p&gt;You typed the code. You're sure you typed it right. The site says "Invalid or expired code." You request a new one. Same thing.&lt;/p&gt;

&lt;p&gt;OTP failures are one of the most frustrating UX patterns on the modern web, because they fail silently and the error messages are useless. Here are the seven real reasons OTP verification fails, in order of how often we actually see them in user reports and our own debugging.&lt;/p&gt;

&lt;h1&gt;
  
  
  1. The Code Already Expired
&lt;/h1&gt;

&lt;p&gt;OTPs typically live 5–10 minutes. By the time you opened the email, read the code, switched tabs, and pasted it, you may have burned 9 minutes of the window — especially if the email was delayed in transit.&lt;/p&gt;

&lt;p&gt;Fix: request a new code. Type it immediately. Don't go make coffee.&lt;/p&gt;

&lt;p&gt;Developer note: if you're building an OTP flow, surface the exact expiry time in the email and the form. "This code expires at 14:32 UTC" beats "expires in 10 minutes" by a mile.&lt;/p&gt;

&lt;h1&gt;
  
  
  2. The Code Was Already Used
&lt;/h1&gt;

&lt;p&gt;OTPs are typically single-use. If you mis-clicked Verify twice, or if a browser autofill submitted the form once and then again, the second attempt fails — even though the code is correct.&lt;/p&gt;

&lt;p&gt;Fix: request a new code. Don't double-click. Watch for autofill.&lt;/p&gt;

&lt;h1&gt;
  
  
  3. The Email Was Silently Rejected
&lt;/h1&gt;

&lt;p&gt;If you used a disposable email address, the site may have rejected your address at submission time without telling you. The form said "we sent a code"; nothing was actually sent.&lt;/p&gt;

&lt;p&gt;Fix: if you suspect this, try with an email alias instead of a disposable address. Many sites accept aliases while blocking disposable domains.&lt;/p&gt;

&lt;h1&gt;
  
  
  4. The Email Went to a Spam Folder You Can't See
&lt;/h1&gt;

&lt;p&gt;For temp mail users, this is silent death — temp inboxes don't have spam folders, so a filtered message is just gone. For real inbox users, it's a 30-second hunt.&lt;/p&gt;

&lt;p&gt;Fix: check spam in your real inbox. For temp mail, regenerate the address and try again from a different provider. See "Receive OTP Codes with Temp Mail".&lt;/p&gt;

&lt;h1&gt;
  
  
  5. You're Verifying With the Wrong Address
&lt;/h1&gt;

&lt;p&gt;Some flows let you request OTPs for any address you type, then verify by typing the address plus the code. If you typed the address slightly differently the second time (capitalization, trailing space, alias variant), the code won't match.&lt;/p&gt;

&lt;p&gt;Fix: copy-paste both the address and the code. Never retype.&lt;/p&gt;

&lt;h1&gt;
  
  
  6. The Site's Clock Is Wrong
&lt;/h1&gt;

&lt;p&gt;This is rare on modern infrastructure but still happens — especially on self-hosted apps and small SaaS. If the server's clock is skewed by more than the OTP TTL, valid codes appear expired.&lt;/p&gt;

&lt;p&gt;Fix: nothing you can do from outside. Email the site's support.&lt;/p&gt;

&lt;h1&gt;
  
  
  7. Rate Limiting
&lt;/h1&gt;

&lt;p&gt;If you've requested 5 codes in 10 minutes, most senders silently stop delivering. The code "didn't arrive" because the system refused to send it.&lt;/p&gt;

&lt;p&gt;Fix: wait 10 minutes. Don't spam the "resend code" button.&lt;/p&gt;

&lt;h1&gt;
  
  
  How to Diagnose, in Order
&lt;/h1&gt;

&lt;p&gt;If your OTP is failing, walk this list:&lt;/p&gt;

&lt;p&gt;Did the email actually arrive? Check the timestamp.&lt;br&gt;
Are you within the expiry window?&lt;br&gt;
Have you tried this exact code before?&lt;br&gt;
Is the email address you're verifying with exactly the one the code was sent to?&lt;br&gt;
Did you request more than 3 codes in the last 10 minutes?&lt;br&gt;
Are you using a disposable address that the site silently rejects?&lt;br&gt;
Most failures resolve at step 1 or 2.&lt;/p&gt;
&lt;h1&gt;
  
  
  OTP Best Practices for Developers
&lt;/h1&gt;

&lt;p&gt;If you're building an OTP flow, these are the patterns that hurt users least:&lt;/p&gt;

&lt;p&gt;Use 6 digits, not 4. 4 digits is brute-forceable in seconds.&lt;br&gt;
Set TTL to 10 minutes, not 60 seconds. A minute is hostile.&lt;br&gt;
Allow at least 5 attempts per code. Typos happen.&lt;br&gt;
Rate-limit at 3 codes per 10 minutes. Enough to handle real users, blocks abuse.&lt;br&gt;
Don't invalidate the code on a typo. Invalidate after 5 failed attempts or on success.&lt;br&gt;
Show the expiry time in the form. Not just "expires soon."&lt;br&gt;
Send the code in the subject line. Mobile users see it without opening the email.&lt;br&gt;
Echo the requesting IP / device in the email. Helps users spot account-takeover attempts.&lt;br&gt;
For testing your own OTP flow, the YoBox Temp Mail tool gives you a disposable inbox in under a second; pair it with the Webhook Tester if your verify endpoint also fires backend webhooks (some auth providers send a "user.verified" webhook you'll want to assert on).&lt;/p&gt;
&lt;h1&gt;
  
  
  A Minimal Test Loop
&lt;/h1&gt;

&lt;p&gt;If you're QA'ing OTPs in CI, this is the pattern:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="nf"&gt;test&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;signup with OTP&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;page&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;address&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;token&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;tempMail&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;createInbox&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;goto&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/signup&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;fill&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;[name=email]&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;address&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;click&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;text=Send code&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;code&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;tempMail&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;pollForCode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;token&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="na"&gt;timeout&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;30&lt;/span&gt;&lt;span class="nx"&gt;_000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="na"&gt;pattern&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sr"&gt;/&lt;/span&gt;&lt;span class="se"&gt;\b(\d{6})\b&lt;/span&gt;&lt;span class="sr"&gt;/&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;fill&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;[name=otp]&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;code&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;click&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;text=Verify&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;toHaveURL&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/dashboard&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Full walkthrough in "Email Testing Guide for Developers".&lt;/p&gt;

&lt;h1&gt;
  
  
  OTP vs Magic Links
&lt;/h1&gt;

&lt;p&gt;A close cousin: magic links. Same idea (one-time token in email), different UX (you click instead of type). The failure modes are nearly identical, with one addition: magic links break when opened in a different browser than the one that requested them. If you request the link on desktop and open it on mobile, many implementations refuse.&lt;/p&gt;

&lt;p&gt;For testing, YoBox Temp Mail handles both — you can extract a 6-digit code with a regex or fetch the link and follow it programmatically.&lt;/p&gt;

&lt;h1&gt;
  
  
  FAQ
&lt;/h1&gt;

&lt;p&gt;Why does Discord say my OTP is invalid even when it's right?&lt;br&gt;
Most often: you used a disposable address Discord silently rejected; the email never arrived. See "Temporary Email for Discord".&lt;/p&gt;

&lt;p&gt;Why does the same code work in Chrome but not Safari?&lt;br&gt;
Likely a cookie / session mismatch. The code is tied to a session ID stored in a cookie; switching browsers breaks the link.&lt;/p&gt;

&lt;p&gt;Can I extend the OTP expiry?&lt;br&gt;
Not as a user. As a developer, yes — but anything over 15 minutes hurts security.&lt;/p&gt;

&lt;p&gt;Why does my OTP arrive twice?&lt;br&gt;
Some senders retry on transient failures. Use the first code; the second is usually the same value resent.&lt;/p&gt;

&lt;p&gt;Is SMS OTP more reliable than email OTP?&lt;br&gt;
Generally yes for delivery speed (SMS is often instant), but SMS has its own failure modes (SIM swap, carrier filtering) and is more expensive for the sender.&lt;/p&gt;

&lt;h1&gt;
  
  
  Bottom Line
&lt;/h1&gt;

&lt;p&gt;OTPs fail for boring reasons more often than exciting ones — expiry, double-clicks, blocklisted disposable domains, rate limits. Walk the diagnostic list before assuming the site is broken. If you're building OTP flows, optimize for human typos and slow inboxes, not the happy path.&lt;/p&gt;

&lt;h1&gt;
  
  
  YoBox Team
&lt;/h1&gt;

&lt;p&gt;Builder behind YoBox — a privacy-first toolbox for developers and QA engineers covering disposable email, webhook capture, regex, secure passwords, Docker, and end-to-end testing.&lt;/p&gt;

</description>
      <category>authentication</category>
      <category>security</category>
      <category>javascript</category>
      <category>testing</category>
    </item>
    <item>
      <title>Temporary Email for Developers: The Definitive Guide</title>
      <dc:creator>yobox</dc:creator>
      <pubDate>Sun, 09 Aug 2026 16:53:19 +0000</pubDate>
      <link>https://dev.to/yobox/temporary-email-for-developers-the-definitive-guide-2nop</link>
      <guid>https://dev.to/yobox/temporary-email-for-developers-the-definitive-guide-2nop</guid>
      <description>&lt;p&gt;Most articles about temp mail are written for people who want to dodge marketing emails. This one isn't. This is the developer's guide to disposable email — how it fits into your workflow, the patterns that scale, and the specific tools that pair with the rest of a modern stack.&lt;/p&gt;

&lt;p&gt;By the end you'll know exactly when to reach for a disposable inbox, how to plug it into CI, how to pair it with webhook testing, and how to keep your real inbox sane while you build.&lt;/p&gt;

&lt;h1&gt;
  
  
  What Developers Actually Use Temp Mail For
&lt;/h1&gt;

&lt;p&gt;The use cases that matter for working developers:&lt;/p&gt;

&lt;p&gt;Testing your own signup flows. Don't sign up to your app 400 times with your real Gmail.&lt;br&gt;
CI-driven E2E tests. Cypress, Playwright, or Postman tests that need to receive a real OTP.&lt;br&gt;
OAuth provider testing. New OAuth provider integrations need test accounts on those providers.&lt;br&gt;
Beta product evaluation. You want to try a SaaS product without putting it on your real email forever.&lt;br&gt;
Disposable accounts for sketchy services. That npm package's signup wall, that Stack Overflow clone, that AI tool with a 7-day trial.&lt;br&gt;
Bot / service accounts. A CI service that needs its own identity.&lt;br&gt;
Verifying email-required signup walls. Reading a Reddit thread, downloading a PDF.&lt;br&gt;
For everything in this list, disposable email is the right tool. For things not in this list (your own production accounts, anything with billing, anything you want to recover), use an alias or real email — see "Disposable Email vs Real Email vs Aliases".&lt;/p&gt;

&lt;h1&gt;
  
  
  The Developer Toolkit
&lt;/h1&gt;

&lt;p&gt;The four tools every developer should have set up:&lt;/p&gt;

&lt;p&gt;Tool    Use for&lt;br&gt;
Real email  Your identity, work, billing, recovery&lt;br&gt;
Email alias service (SimpleLogin / Apple Hide My Email) Per-service permanent addresses&lt;br&gt;
YoBox Temp Mail One-off testing, CI, throwaway signups&lt;br&gt;
Local SMTP catcher (Mailhog / MailCatcher)  Local dev where email shouldn't leave the machine&lt;br&gt;
Each has a clear role. Mixing them up is how you end up with 12 password resets for the same forum in your work inbox.&lt;/p&gt;

&lt;h1&gt;
  
  
  Pattern 1: Testing Your Own Signup Flow
&lt;/h1&gt;

&lt;p&gt;Your app sends an OTP at signup. You want to test the flow without using your personal email.&lt;/p&gt;

&lt;p&gt;The pattern:&lt;/p&gt;

&lt;p&gt;Open YoBox Temp Mail in a new tab.&lt;br&gt;
Copy the address.&lt;br&gt;
Submit your signup form with it.&lt;br&gt;
Read the OTP from the disposable inbox.&lt;br&gt;
Paste back.&lt;br&gt;
Inspect the resulting user state.&lt;br&gt;
For a manual debug session, this is the fastest possible loop. For automated tests, see pattern 2.&lt;/p&gt;

&lt;h1&gt;
  
  
  Pattern 2: E2E Tests in CI
&lt;/h1&gt;

&lt;p&gt;For CI, you want the same loop fully scripted. The YoBox Temp Mail tool exposes a JSON API designed for this:&lt;/p&gt;

&lt;p&gt;`&lt;code&gt;ts&lt;br&gt;
// Create inbox&lt;br&gt;
const inbox = await fetch('https://yobox.dev/mail/account', {&lt;br&gt;
method: 'POST',&lt;br&gt;
headers: { 'content-type': 'application/json' },&lt;br&gt;
body: JSON.stringify({&lt;br&gt;
address: test-${Date.now()}@yobox-test.dev&lt;/code&gt;,&lt;br&gt;
password: crypto.randomUUID(),&lt;br&gt;
}),&lt;br&gt;
}).then(r =&amp;gt; r.json());&lt;/p&gt;

&lt;p&gt;// Trigger signup&lt;br&gt;
await fetch('&lt;a href="https://myapp.com/signup" rel="noopener noreferrer"&gt;https://myapp.com/signup&lt;/a&gt;', {&lt;br&gt;
method: 'POST',&lt;br&gt;
headers: { 'content-type': 'application/json' },&lt;br&gt;
body: JSON.stringify({ email: inbox.address }),&lt;br&gt;
});&lt;/p&gt;

&lt;p&gt;// Poll for message&lt;br&gt;
const msg = await pollForEmail(inbox.token, 30_000);&lt;br&gt;
const code = msg.text.match(/\b(\d{6})\b/)?.[1];&lt;/p&gt;

&lt;p&gt;// Verify&lt;br&gt;
await fetch('&lt;a href="https://myapp.com/verify" rel="noopener noreferrer"&gt;https://myapp.com/verify&lt;/a&gt;', {&lt;br&gt;
method: 'POST',&lt;br&gt;
headers: { 'content-type': 'application/json' },&lt;br&gt;
body: JSON.stringify({ email: inbox.address, code }),&lt;br&gt;
});&lt;br&gt;
`&lt;code&gt;&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Full Cypress and Playwright versions in "How to Test Email Flows Without a Real Inbox".&lt;/p&gt;

&lt;h1&gt;
  
  
  Pattern 3: Pair with Webhook Capture
&lt;/h1&gt;

&lt;p&gt;Many signup flows fire backend webhooks alongside the email — Stripe's customer.created, your own user.verified, Mailgun's delivery events. To test the full async loop, pair Temp Mail with the Webhook Tester:&lt;/p&gt;

&lt;p&gt;Generate a disposable inbox.&lt;br&gt;
Generate a webhook capture URL.&lt;br&gt;
Configure your app to use both.&lt;br&gt;
Trigger the signup.&lt;br&gt;
Assert on the OTP email arriving.&lt;br&gt;
Assert on the downstream webhook firing.&lt;br&gt;
This is how you catch bugs like "the email goes out but the webhook never fires because the after-hooks crashed silently."&lt;/p&gt;

&lt;p&gt;See "Cypress E2E with YoBox Disposable Email and Webhook Tester".&lt;/p&gt;

&lt;h1&gt;
  
  
  Pattern 4: OAuth Provider Testing
&lt;/h1&gt;

&lt;p&gt;You're integrating Sign in with GitHub. Your test needs a fresh GitHub account.&lt;/p&gt;

&lt;p&gt;Honest answer: don't use disposable email for this. GitHub blocks it. Use an alias for a long-term test account. See "Temporary Email for GitHub".&lt;/p&gt;

&lt;p&gt;For providers that do allow disposable email (some smaller OAuth providers, niche identity services), YoBox Temp Mail works fine — same flow as the signup pattern.&lt;/p&gt;

&lt;h1&gt;
  
  
  Pattern 5: Beta / Trial Accounts
&lt;/h1&gt;

&lt;p&gt;You want to try a new product. You don't want them having your real email forever. Three options:&lt;/p&gt;

&lt;p&gt;Alias for products you might actually adopt.&lt;br&gt;
Disposable for products you'll evaluate once.&lt;br&gt;
Real email if the product needs to talk to you long-term (billing, support).&lt;br&gt;
When in doubt, alias. Disposable for true one-offs.&lt;/p&gt;

&lt;h1&gt;
  
  
  Pattern 6: Bot / Service Accounts
&lt;/h1&gt;

&lt;p&gt;You need an account for a CI process to use. Two clean options:&lt;/p&gt;

&lt;p&gt;Custom-domain alias&lt;br&gt;
Set up &lt;a href="mailto:ci-bot@yourcompany.dev"&gt;ci-bot@yourcompany.dev&lt;/a&gt;. Forwards to a real address. Survives forever. This is the right answer for any long-lived bot.&lt;/p&gt;

&lt;p&gt;YoBox + persistence&lt;br&gt;
For short-lived CI accounts (per-test isolation), generate a fresh YoBox inbox per test run. Don't try to make it long-lived.&lt;/p&gt;

&lt;h1&gt;
  
  
  Reducing Inbox Spam
&lt;/h1&gt;

&lt;p&gt;A side benefit of using disposable email aggressively: your real inbox stays clean. The compound effect over 5 years is the difference between an inbox that takes 10 minutes to triage daily and one that takes 60.&lt;/p&gt;

&lt;p&gt;The rule that works: default to disposable for everything that isn't tier 1 (bank, work, government, password manager). Use aliases for anything you'd want to receive mail from. Reserve your real email for the small set of accounts that matter most.&lt;/p&gt;

&lt;h1&gt;
  
  
  What to Build Into Your Dotfiles
&lt;/h1&gt;

&lt;p&gt;If you do a lot of email testing, automate the boring parts:&lt;/p&gt;

&lt;p&gt;A shell function mail-temp that hits the YoBox API and copies a fresh address to your clipboard.&lt;br&gt;
A Cypress / Playwright command (cy.disposableEmail()) that returns an inbox object.&lt;br&gt;
A snippet in your test fixture library that polls for OTPs with a regex.&lt;br&gt;
A Slack / Discord shortcut that opens YoBox Temp Mail and the Webhook Tester side by side.&lt;br&gt;
Once these are muscle memory, email testing stops being friction.&lt;/p&gt;

&lt;h1&gt;
  
  
  Security Reminders
&lt;/h1&gt;

&lt;p&gt;Even for developers, temp mail isn't a security tool:&lt;/p&gt;

&lt;p&gt;Inboxes are addressable. Anyone who knows the address can read it (modulo token-based auth).&lt;br&gt;
Don't reuse addresses for unrelated accounts. Password reset cross-pollution is a real risk.&lt;br&gt;
Don't put production secrets through temp mail. It's for fake test users, not real credentials.&lt;br&gt;
Rotate addresses per test run in CI. Avoid shared state.&lt;/p&gt;

&lt;h1&gt;
  
  
  FAQ
&lt;/h1&gt;

&lt;p&gt;Can I use temp mail for my own production app's emails?&lt;br&gt;
No. Production accounts need persistent inboxes. Use temp mail to test the email flow, not as the actual receiver for real users.&lt;/p&gt;

&lt;p&gt;How do I avoid hitting rate limits?&lt;br&gt;
Generate fresh addresses per test. Don't reuse one address for 50 sequential signups — that's what triggers sender-side abuse heuristics.&lt;/p&gt;

&lt;p&gt;Does YoBox Temp Mail work in CI / GitHub Actions?&lt;br&gt;
Yes — no auth, no captcha, no rate limit on normal use. Designed for CI.&lt;/p&gt;

&lt;p&gt;Can I receive attachments?&lt;br&gt;
The API exposes message bodies; attachments are visible in the browser UI but not currently programmatically downloadable.&lt;/p&gt;

&lt;p&gt;Is it safe to use disposable email for OAuth testing?&lt;br&gt;
Yes for providers that accept it (most small / mid). No for providers that block it (GitHub, Google, Discord). See provider-specific posts.&lt;/p&gt;

&lt;h1&gt;
  
  
  Bottom Line
&lt;/h1&gt;

&lt;p&gt;Temp mail is one of the most underrated developer tools. Used right, it cleans up your real inbox, accelerates your test loop, and pairs with webhook capture for full async coverage of complex flows. The YoBox Temp Mail tool was built specifically for developer workflows — fast, scriptable, no nonsense. Add it to your toolkit and stop signing up to your own app with &lt;a href="mailto:yourname+test001@gmail.com"&gt;yourname+test001@gmail.com&lt;/a&gt;.&lt;/p&gt;

&lt;h1&gt;
  
  
  YoBox Team
&lt;/h1&gt;

&lt;p&gt;Builder behind YoBox — a privacy-first toolbox for developers and QA engineers covering disposable email, webhook capture, regex, secure passwords, Docker, and end-to-end testing.&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>programming</category>
      <category>security</category>
      <category>testing</category>
    </item>
    <item>
      <title>Testing API Responses Seamlessly with Postman and YoBox</title>
      <dc:creator>yobox</dc:creator>
      <pubDate>Wed, 05 Aug 2026 17:23:38 +0000</pubDate>
      <link>https://dev.to/yobox/testing-api-responses-seamlessly-with-postman-and-yobox-548l</link>
      <guid>https://dev.to/yobox/testing-api-responses-seamlessly-with-postman-and-yobox-548l</guid>
      <description>&lt;p&gt;Postman is great at one thing: firing a request and asserting on the response body. It's not great at the moment immediately after, when your API quietly sends an email, queues a job, or POSTs to a partner webhook. That side-effect surface is where most production bugs hide, and where most Postman collections silently stop testing.&lt;/p&gt;

&lt;p&gt;YoBox closes the gap. A disposable inbox you can poll over HTTP. A webhook URL that records everything. Both work natively with pm.sendRequest and Newman, no SDK required.&lt;/p&gt;

&lt;p&gt;This guide walks through the patterns we use for serious Postman + YoBox API testing.&lt;/p&gt;

&lt;h1&gt;
  
  
  The mental model
&lt;/h1&gt;

&lt;p&gt;Your API does three kinds of work in response to a request:&lt;/p&gt;

&lt;p&gt;Returns a body — Postman already tests this.&lt;br&gt;
Sends an email — YoBox Temp Mail tests this.&lt;br&gt;
Hits an outbound webhook — YoBox Webhook Tester tests this.&lt;br&gt;
A complete API assertion covers all three.&lt;/p&gt;
&lt;h1&gt;
  
  
  Setting up the environment
&lt;/h1&gt;

&lt;p&gt;Create two collection variables:&lt;/p&gt;

&lt;p&gt;yoboxBase → &lt;a href="https://yobox.dev/api" rel="noopener noreferrer"&gt;https://yobox.dev/api&lt;/a&gt;&lt;br&gt;
apiBase → your service URL&lt;br&gt;
Add a startup folder with two requests:&lt;/p&gt;

&lt;p&gt;POST {{yoboxBase}}/mail/new&lt;br&gt;
POST {{yoboxBase}}/hooks/new&lt;br&gt;
Each saves id and address / url into collection variables. Every subsequent request can reference them.&lt;/p&gt;
&lt;h1&gt;
  
  
  Asserting response shape
&lt;/h1&gt;

&lt;p&gt;Postman's Tests tab handles the basics:&lt;/p&gt;

&lt;p&gt;pm.test("status 201", () =&amp;gt; pm.response.to.have.status(201));&lt;br&gt;
pm.test("returns user id", () =&amp;gt; {&lt;br&gt;
  const body = pm.response.json();&lt;br&gt;
  pm.expect(body.id).to.match(/^[0-9a-f-]{36}$/);&lt;br&gt;
  pm.expect(body.email).to.eql(pm.collectionVariables.get("inboxAddress"));&lt;br&gt;
});&lt;br&gt;
Use the Regex Patterns cheat sheet for common assertions (UUID, JWT, ISO timestamps, currency).&lt;/p&gt;
&lt;h1&gt;
  
  
  Asserting email side effects
&lt;/h1&gt;

&lt;p&gt;The email arrives after the request returns. Wait for it in a pre-request script on the next request:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;pm&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;collectionVariables&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;inboxId&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;base&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;pm&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;collectionVariables&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;yoboxBase&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;wait&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;ms&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Promise&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;r&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;setTimeout&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;r&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;ms&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;

&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;poll&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Promise&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;resolve&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;reject&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;
&lt;span class="nx"&gt;pm&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sendRequest&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;$&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;base&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="sr"&gt;/mail/&lt;/span&gt;&lt;span class="nx"&gt;$&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="sr"&gt;/messages, &lt;/span&gt;&lt;span class="se"&gt;(&lt;/span&gt;&lt;span class="sr"&gt;err, res&lt;/span&gt;&lt;span class="se"&gt;)&lt;/span&gt;&lt;span class="sr"&gt; =&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="nf"&gt;reject&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;resolve&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="k"&gt;for &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;i&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nx"&gt;i&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="mi"&gt;20&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="o"&gt;++&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;poll&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;messages&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;msg&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;messages&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
&lt;span class="nx"&gt;pm&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;collectionVariables&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;otp&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;msg&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;text&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;match&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sr"&gt;/&lt;/span&gt;&lt;span class="se"&gt;\b\d{6}\b&lt;/span&gt;&lt;span class="sr"&gt;/&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;""&lt;/span&gt;&lt;span class="p"&gt;])[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]);&lt;/span&gt;
&lt;span class="nx"&gt;pm&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;collectionVariables&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;emailSubject&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;msg&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;subject&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;wait&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1500&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Email timeout&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;})();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then assert the captured values:&lt;/p&gt;

&lt;p&gt;pm.test("welcome email arrived", () =&amp;gt; {&lt;br&gt;
  pm.expect(pm.collectionVariables.get("emailSubject")).to.include("Welcome");&lt;br&gt;
  pm.expect(pm.collectionVariables.get("otp")).to.match(/^\d{6}$/);&lt;br&gt;
});&lt;/p&gt;

&lt;h1&gt;
  
  
  Asserting webhook side effects
&lt;/h1&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// In the Tests tab of the trigger request&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;hookId&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;pm&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;collectionVariables&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;hookId&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;base&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;pm&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;collectionVariables&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;yoboxBase&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="nf"&gt;setTimeout&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="nx"&gt;pm&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sendRequest&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;$&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;base&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="sr"&gt;/hooks/&lt;/span&gt;&lt;span class="nx"&gt;$&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;hookId&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="nx"&gt;pm&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;test&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;webhook fired&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;pm&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;count&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;to&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;be&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;above&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
&lt;span class="nx"&gt;pm&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;test&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;payload shape&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;body&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;parse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;requests&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nx"&gt;pm&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;to&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;eql&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;invoice.paid&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="mi"&gt;2000&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For Newman in CI, prefer a dedicated assertion request after the trigger so the timing is deterministic.&lt;/p&gt;

&lt;h1&gt;
  
  
  Comparison table
&lt;/h1&gt;

&lt;p&gt;Test target Postman alone   Postman + YoBox&lt;br&gt;
Response body shape Yes Yes&lt;br&gt;
Response timing Yes Yes&lt;br&gt;
Email delivery  No  Yes&lt;br&gt;
Email content   No  Yes&lt;br&gt;
Webhook delivery    No  Yes&lt;br&gt;
Webhook payload shape   No  Yes&lt;/p&gt;

&lt;h1&gt;
  
  
  Running in CI
&lt;/h1&gt;

&lt;ul&gt;
&lt;li&gt;run: npx newman run collection.json -e env.json --reporters cli,junit
env:
YOBOX_BASE: &lt;a href="https://yobox.dev/api" rel="noopener noreferrer"&gt;https://yobox.dev/api&lt;/a&gt;
Newman supports pre-request scripts and pm.sendRequest exactly like Postman, so the patterns above run unchanged.&lt;/li&gt;
&lt;/ul&gt;

&lt;h1&gt;
  
  
  Pairs with
&lt;/h1&gt;

&lt;p&gt;Cypress + YoBox for browser-side flows that depend on API responses.&lt;br&gt;
Playwright + YoBox for cross-browser API + UI tests.&lt;br&gt;
Realistic Mock Data for body fixtures.&lt;br&gt;
Password Generator for credentials.&lt;/p&gt;

&lt;h1&gt;
  
  
  Common pitfalls
&lt;/h1&gt;

&lt;p&gt;Trusting a 200 as proof of side effects. A 200 says the request was accepted, not that the side effect ran.&lt;br&gt;
Polling too fast. 1.5 s is the right interval for email; 500 ms is the right interval for in-process webhooks.&lt;br&gt;
Forgetting environments. Use Postman environments for apiBase so the same collection runs against staging and production.&lt;br&gt;
Asserting on HTML email bodies. Always parse the plain-text part.&lt;/p&gt;

&lt;h1&gt;
  
  
  FAQ
&lt;/h1&gt;

&lt;p&gt;Can I test gRPC or GraphQL?&lt;br&gt;
Yes — Postman supports both. The YoBox plumbing is identical because it's just HTTP polling.&lt;/p&gt;

&lt;p&gt;Does Postman's Flows feature work with YoBox?&lt;br&gt;
Yes — model the wait-for-email step as a delay + HTTP request node.&lt;/p&gt;

&lt;p&gt;How do I share a collection without leaking the YoBox URL?&lt;br&gt;
Use an environment variable, not a hard-coded base.&lt;/p&gt;

&lt;p&gt;What about file attachments?&lt;br&gt;
The messages endpoint returns attachment metadata; download via a follow-up request.&lt;/p&gt;

&lt;h1&gt;
  
  
  Conclusion
&lt;/h1&gt;

&lt;p&gt;A Postman collection that only asserts on response bodies tests half your API. Wire YoBox into pre-request scripts and the Tests tab, and the same collection now verifies emails and webhooks too — in Postman, in Newman, in CI. Two fixtures' worth of code, dramatically more coverage.&lt;/p&gt;

&lt;p&gt;Further reading: The Complete Postman Guide, Cypress + YoBox, Regex Patterns Every QA Engineer Should Memorize.&lt;/p&gt;

&lt;h1&gt;
  
  
  Advanced: contract testing with YoBox webhooks
&lt;/h1&gt;

&lt;p&gt;Treat the YoBox-captured payload as the source of truth for your partner's contract. Snapshot the JSON shape on a green build and fail subsequent runs that drift from it.&lt;/p&gt;

&lt;h1&gt;
  
  
  Advanced: response time SLAs
&lt;/h1&gt;

&lt;p&gt;Pair \pm.expect(pm.response.responseTime).to.be.below(500)\ with a YoBox-verified side effect to assert both that the API was fast and that the work actually happened.&lt;/p&gt;

&lt;h1&gt;
  
  
  Migration: from manual to monitored
&lt;/h1&gt;

&lt;p&gt;Postman Monitors run collections on a schedule. Once your collection asserts emails and webhooks via YoBox, you can promote the same collection into a monitor and get continuous production verification for free.&lt;/p&gt;

&lt;h1&gt;
  
  
  Reporting
&lt;/h1&gt;

&lt;p&gt;Newman's JUnit output drops cleanly into any CI dashboard. YoBox-backed assertions look identical to response-body assertions in the report, so there's no new vocabulary for QA to learn.&lt;/p&gt;

&lt;h1&gt;
  
  
  A deeper Postman workflow
&lt;/h1&gt;

&lt;p&gt;Postman is famous for one-off requests, but the real value shows up when you treat collections like code: versioned, reviewed, and runnable in CI. The trick is to combine Postman's environment variables with YoBox's ephemeral primitives so every run is hermetic.&lt;/p&gt;

&lt;p&gt;Environment design&lt;br&gt;
Create three environments — local, staging, and ci. Each carries:&lt;/p&gt;

&lt;p&gt;baseUrl — the API under test&lt;br&gt;
hookId — refreshed per run from Webhook Tester&lt;br&gt;
tempEmail — refreshed per run from Temp Mail&lt;br&gt;
runId — a UUID generated in a pre-request script&lt;br&gt;
// Collection-level pre-request&lt;br&gt;
if (!pm.environment.get("runId")) {&lt;br&gt;
  pm.environment.set("runId", crypto.randomUUID());&lt;br&gt;
}&lt;br&gt;
Chaining requests&lt;br&gt;
Postman's request chaining lets you treat a multi-step flow — signup → verify email → create resource → wait for webhook — as a single test artifact. Each step writes to collection variables that downstream steps consume.&lt;/p&gt;

&lt;p&gt;// After "create resource"&lt;br&gt;
const id = pm.response.json().id;&lt;br&gt;
pm.collectionVariables.set("resourceId", id);&lt;br&gt;
pm.test("resource created", () =&amp;gt; pm.expect(id).to.be.a("string"));&lt;br&gt;
Async assertions&lt;br&gt;
Async webhooks are the bane of API testing. Polling the YoBox Webhook Tester from a Postman test gives you a deterministic wait without sleeping arbitrary durations.&lt;/p&gt;

&lt;p&gt;const url = &lt;code&gt;https://yobox.dev/api/hooks/${pm.environment.get("hookId")}&lt;/code&gt;;&lt;br&gt;
const deadline = Date.now() + 15000;&lt;br&gt;
(function poll() {&lt;br&gt;
  pm.sendRequest(url, (err, res) =&amp;gt; {&lt;br&gt;
    const hits = res &amp;amp;&amp;amp; res.json().requests || [];&lt;br&gt;
    if (hits.length &amp;gt; 0) {&lt;br&gt;
      pm.test("webhook received", () =&amp;gt; pm.expect(hits[0].method).to.eql("POST"));&lt;br&gt;
    } else if (Date.now() &amp;lt; deadline) {&lt;br&gt;
      setTimeout(poll, 500);&lt;br&gt;
    } else {&lt;br&gt;
      pm.test("webhook received", () =&amp;gt; pm.expect.fail("timeout"));&lt;br&gt;
    }&lt;br&gt;
  });&lt;br&gt;
})();&lt;/p&gt;

&lt;h1&gt;
  
  
  Postman + Newman in CI/CD
&lt;/h1&gt;

&lt;p&gt;Newman is Postman's CLI runner and the bridge between Postman the IDE and your pipeline. A typical GitHub Actions job:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;name: API contract tests
run: |
npx newman run ./postman/collection.json \
  -e ./postman/env.ci.json \
  --reporters cli,junit \
  --reporter-junit-export junit.xml
Combine that with the Docker builder pattern for reproducible runners that include Newman pre-installed.&lt;/li&gt;
&lt;/ul&gt;

&lt;h1&gt;
  
  
  Comparison: Postman vs. alternatives
&lt;/h1&gt;

&lt;p&gt;Tool    GUI CLI Async webhooks  Best for&lt;br&gt;
Postman Yes Newman  Manual polling  Collaborative API exploration&lt;br&gt;
Insomnia    Yes inso    Plugin needed   Lean, scriptable workflows&lt;br&gt;
Hurl    No  hurl    Limited Plain-text, git-friendly tests&lt;br&gt;
Bruno   Yes bru Manual  Offline-first, file-based specs&lt;br&gt;
k6  No  k6  Yes Load + functional combined runs&lt;br&gt;
The "right" tool depends on team shape. If your QA engineers live in a GUI and your devs live in a terminal, Postman + Newman bridges both.&lt;/p&gt;

&lt;h1&gt;
  
  
  Troubleshooting
&lt;/h1&gt;

&lt;p&gt;My webhook never arrives.&lt;br&gt;
Inspect outbound calls from your service with a packet log or your provider's delivery dashboard. The most common cause is a wrong URL pasted into an environment variable.&lt;/p&gt;

&lt;p&gt;Tests pass locally and fail in CI.&lt;br&gt;
Almost always an environment file mismatch. Use newman run ... --env-var key=value to override per-run instead of editing committed JSON.&lt;/p&gt;

&lt;p&gt;Postman scripts time out.&lt;br&gt;
Default request timeout is 0 (no timeout). For polling loops set a hard deadline in script as shown above.&lt;/p&gt;

&lt;h1&gt;
  
  
  FAQ
&lt;/h1&gt;

&lt;p&gt;Can Postman replace Cypress or Playwright?&lt;br&gt;
No. Postman covers the API surface; Cypress and Playwright cover the browser. They complement each other — API tests are fast and exhaustive, UI tests are slow and selective.&lt;/p&gt;

&lt;p&gt;Does Newman support parallel runs?&lt;br&gt;
Not natively. Use newman-run-parallel or run multiple Newman processes in your CI matrix, each with a distinct collection slice.&lt;/p&gt;

&lt;p&gt;How do I share a Postman collection without leaking secrets?&lt;br&gt;
Export the collection but never the environment. Commit the collection JSON, share a sanitized env.example.json, and let each developer create their own env.local.json ignored by git.&lt;/p&gt;

&lt;p&gt;Is there a free alternative for Postman Cloud?&lt;br&gt;
The Postman desktop app is free for collections and Newman runs. Cloud features like Mock Server and Monitor are paid; for those, YoBox's Webhook Tester covers most ad-hoc needs at zero cost.&lt;/p&gt;

</description>
      <category>postman</category>
      <category>api</category>
      <category>testing</category>
      <category>webdev</category>
    </item>
    <item>
      <title>After months of building, testing, and refining, I'm excited to launch **YoBox** on Product Hunt!</title>
      <dc:creator>yobox</dc:creator>
      <pubDate>Mon, 03 Aug 2026 16:44:00 +0000</pubDate>
      <link>https://dev.to/yobox/after-months-of-building-testing-and-refining-im-excited-to-launch-yobox-on-product-hunt-5dl</link>
      <guid>https://dev.to/yobox/after-months-of-building-testing-and-refining-im-excited-to-launch-yobox-on-product-hunt-5dl</guid>
      <description>&lt;p&gt;🚀 Hey developers!&lt;/p&gt;

&lt;p&gt;After months of building, testing, and refining, I'm excited to launch &lt;strong&gt;YoBox&lt;/strong&gt; on Product Hunt!&lt;/p&gt;

&lt;p&gt;YoBox is an all-in-one developer toolkit designed to save time by bringing the tools you use every day into one clean workspace. No ads overload, no unnecessary complexity—just fast, practical tools that help you get work done.&lt;/p&gt;

&lt;p&gt;✨ Some of the available tools include:&lt;br&gt;
• Temporary Email&lt;br&gt;
• Webhook Tester&lt;br&gt;
• Docker Compose Builder&lt;br&gt;
• JSON Formatter &amp;amp; Validator&lt;br&gt;
• Regex Tools&lt;br&gt;
• Password Generator&lt;br&gt;
• Image &amp;amp; File Utilities&lt;br&gt;
• And many more...&lt;/p&gt;

&lt;p&gt;I'd be honored if you could join the launch as a &lt;strong&gt;Maker&lt;/strong&gt; and support the project.&lt;/p&gt;

&lt;p&gt;👉 Accept the invitation here:&lt;br&gt;
&lt;a href="https://www.producthunt.com/posts/yobox-developer-tools-email/maker-invite?code=QN4gDN" rel="noopener noreferrer"&gt;https://www.producthunt.com/posts/yobox-developer-tools-email/maker-invite?code=QN4gDN&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Every maker, upvote, comment, and piece of feedback helps improve YoBox and reach more developers around the world.&lt;/p&gt;

&lt;p&gt;Thank you so much for your support! ❤️&lt;/p&gt;

&lt;h1&gt;
  
  
  ProductHunt #BuildInPublic #IndieHackers #WebDevelopment #DeveloperTools #Programming #Coding #OpenSource #SaaS #Webhooks #Docker #JSON #Regex #Productivity #DevCommunity #LaunchDay #Startups #Tech
&lt;/h1&gt;

</description>
    </item>
    <item>
      <title>Stripe and PayPal Webhook Testing: A Practical Guide</title>
      <dc:creator>yobox</dc:creator>
      <pubDate>Fri, 31 Jul 2026 16:44:35 +0000</pubDate>
      <link>https://dev.to/yobox/stripe-and-paypal-webhook-testing-a-practical-guide-4ph7</link>
      <guid>https://dev.to/yobox/stripe-and-paypal-webhook-testing-a-practical-guide-4ph7</guid>
      <description>&lt;p&gt;Payment webhooks are the highest-stakes webhooks you'll ever wire up. A missed payment_intent.succeeded means a customer paid and didn't get their thing. A double-processed charge.refunded means you accidentally refunded twice. The bugs hide until they don't.&lt;/p&gt;

&lt;p&gt;This is the practical guide to testing Stripe and PayPal webhooks end-to-end — from inspecting the raw payloads to verifying signatures, simulating failures, and asserting on the full async flow.&lt;/p&gt;

&lt;h1&gt;
  
  
  The Three Layers of Webhook Testing
&lt;/h1&gt;

&lt;p&gt;For payment webhooks specifically:&lt;/p&gt;

&lt;p&gt;Layer   What you're testing How&lt;br&gt;
Inspection  What does the provider actually send?   YoBox Webhook Tester, Stripe CLI&lt;br&gt;
Handler logic   Does my code do the right thing on a known payload? Unit tests with fixture payloads&lt;br&gt;
End-to-end  Does the full flow work, including signature, retries, idempotency? Integration tests against test mode&lt;br&gt;
Skip any layer and bugs leak.&lt;/p&gt;
&lt;h1&gt;
  
  
  Layer 1: Inspect the Payloads
&lt;/h1&gt;

&lt;p&gt;Before writing the handler, see what the provider sends. The docs are usually right; "usually" is a problem when money's involved.&lt;/p&gt;

&lt;p&gt;Stripe&lt;br&gt;
Use the YoBox Webhook Tester:&lt;/p&gt;

&lt;p&gt;Generate a capture URL in YoBox.&lt;br&gt;
In the Stripe dashboard → Developers → Webhooks → Add endpoint.&lt;br&gt;
Paste the YoBox URL. Select the events you care about.&lt;br&gt;
Use the Stripe CLI to trigger test events: stripe trigger payment_intent.succeeded.&lt;br&gt;
Watch the capture log fill in.&lt;br&gt;
You'll see the full payload, including:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Stripe-Signature header (the HMAC you'll verify)&lt;/li&gt;
&lt;li&gt;Stripe-Version header&lt;/li&gt;
&lt;li&gt;JSON body with type, data.object, etc.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Save a few payloads as test fixtures. You'll want them for unit tests.&lt;/p&gt;

&lt;p&gt;PayPal&lt;br&gt;
Same flow, but PayPal's UI is more cumbersome:&lt;/p&gt;

&lt;p&gt;Generate a YoBox capture URL.&lt;br&gt;
In the PayPal Developer dashboard → My Apps &amp;amp; Credentials → your app → "Add webhook."&lt;br&gt;
Paste the URL. Select events.&lt;br&gt;
Use the simulator: Webhook Simulator → choose event type → enter your URL.&lt;br&gt;
Inspect in YoBox.&lt;br&gt;
PayPal payloads are noisier than Stripe's. The signature scheme is also more complex (involves cert chain verification, not just HMAC). Capture early, save fixtures.&lt;/p&gt;
&lt;h1&gt;
  
  
  Layer 2: Unit Test the Handler
&lt;/h1&gt;

&lt;p&gt;With fixtures in hand, write unit tests that hit your handler directly:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;paymentSucceeded&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;./fixtures/stripe-payment-intent-succeeded.json&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="nf"&gt;test&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;handles payment_intent.succeeded&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;handleStripeWebhook&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;paymentSucceeded&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nf"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;status&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;toBe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;processed&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nf"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;orderRepo&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;markPaid&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;toHaveBeenCalledWith&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
&lt;span class="na"&gt;orderId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;paymentSucceeded&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;object&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;metadata&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;order_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="na"&gt;amount&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;paymentSucceeded&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;object&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;amount&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="nf"&gt;test&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;is idempotent on duplicate event&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;handleStripeWebhook&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;paymentSucceeded&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;second&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;handleStripeWebhook&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;paymentSucceeded&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nf"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;second&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;status&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;toBe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;skipped-duplicate&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nf"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;orderRepo&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;markPaid&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;toHaveBeenCalledTimes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="nf"&gt;test&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;rejects bad signature&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
&lt;span class="nf"&gt;handleStripeWebhook&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;paymentSucceeded&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;signature&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;bogus&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt;
&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;rejects&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;toThrow&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sr"&gt;/signature/&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Run on every PR.&lt;/p&gt;

&lt;h1&gt;
  
  
  Layer 3: End-to-End in Test Mode
&lt;/h1&gt;

&lt;p&gt;The most important layer and the one teams most often skip. The flow:&lt;/p&gt;

&lt;p&gt;Spin up your app pointed at Stripe (or PayPal) test mode.&lt;br&gt;
Configure the webhook endpoint to be your app's real handler URL (via ngrok in dev, your staging URL in CI).&lt;br&gt;
Create a real test charge with the Stripe CLI or PayPal sandbox.&lt;br&gt;
Assert on:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The webhook arrived&lt;/li&gt;
&lt;li&gt;Your handler returned 2xx within timeout&lt;/li&gt;
&lt;li&gt;The database state is correct&lt;/li&gt;
&lt;li&gt;Any downstream side effects fired
Using the Stripe CLI in CI
&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Forward webhooks to localhost while running tests&lt;/span&gt;
stripe listen &lt;span class="nt"&gt;--forward-to&lt;/span&gt; http://localhost:3000/webhooks/stripe &amp;amp;

&lt;span class="c"&gt;#Trigger a known event stripe trigger payment_intent.succeeded&lt;/span&gt;
Now your &lt;span class="nb"&gt;test &lt;/span&gt;asserts on the resulting state &lt;span class="sb"&gt;```&lt;/span&gt;
&lt;span class="o"&gt;{&lt;/span&gt;% endraw %&lt;span class="o"&gt;}&lt;/span&gt;

The Stripe CLI handles signature verification and gives you a known webhook secret you can use &lt;span class="k"&gt;in &lt;/span&gt;tests.

Pairing with YoBox &lt;span class="k"&gt;for &lt;/span&gt;capture verification
Sometimes you want to assert that your app sent a webhook downstream — &lt;span class="k"&gt;for &lt;/span&gt;example, a notification to a downstream service after the payment processed. Point that downstream URL at the YoBox Webhook Tester &lt;span class="k"&gt;in &lt;/span&gt;&lt;span class="nb"&gt;test &lt;/span&gt;mode:
&lt;span class="o"&gt;{&lt;/span&gt;% raw %&lt;span class="o"&gt;}&lt;/span&gt;


&lt;span class="sb"&gt;```&lt;/span&gt;ts
&lt;span class="nb"&gt;test&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'after payment, notifies fulfillment service'&lt;/span&gt;, async &lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
const captureUrl &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'https://yobox.dev/webhook/'&lt;/span&gt; + crypto.randomUUID&lt;span class="o"&gt;()&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
await setFulfillmentWebhookUrl&lt;span class="o"&gt;(&lt;/span&gt;captureUrl&lt;span class="o"&gt;)&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

await triggerStripePaymentInTestMode&lt;span class="o"&gt;()&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

const captured &lt;span class="o"&gt;=&lt;/span&gt; await pollForWebhook&lt;span class="o"&gt;(&lt;/span&gt;captureUrl, 30_000&lt;span class="o"&gt;)&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
expect&lt;span class="o"&gt;(&lt;/span&gt;captured.body.event&lt;span class="o"&gt;)&lt;/span&gt;.toBe&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'order.paid'&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
expect&lt;span class="o"&gt;(&lt;/span&gt;captured.body.amount&lt;span class="o"&gt;)&lt;/span&gt;.toBeGreaterThan&lt;span class="o"&gt;(&lt;/span&gt;0&lt;span class="o"&gt;)&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="o"&gt;})&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;h1&gt;
  
  
  Signature Verification: Stripe
&lt;/h1&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;Stripe&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;stripe&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;stripe&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Stripe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;STRIPE_SECRET_KEY&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;POST&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Request&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;signature&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;stripe-signature&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;body&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;text&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt; &lt;span class="c1"&gt;// raw body, not parsed&lt;/span&gt;

&lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Stripe&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Event&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="nx"&gt;event&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;stripe&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;webhooks&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;constructEvent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
&lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="nx"&gt;signature&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;STRIPE_WEBHOOK_SECRET&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;catch &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Response&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;invalid signature&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;status&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;400&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;// Idempotency check&lt;/span&gt;
&lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;wasProcessed&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Response&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;ok&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;status&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;200&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;// Process&lt;/span&gt;
&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;processStripeEvent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;markProcessed&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Response&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;ok&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;status&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;200&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Three things to never skip:&lt;/p&gt;

&lt;p&gt;Use the raw body. Body parsing destroys the signature.&lt;br&gt;
Catch and 400 on invalid signature. Don't 500 (the provider will retry).&lt;br&gt;
Idempotency by event ID. Stripe explicitly recommends this.&lt;/p&gt;
&lt;h1&gt;
  
  
  Signature Verification: PayPal
&lt;/h1&gt;

&lt;p&gt;PayPal is more involved. You need to fetch their cert and verify the signature chain.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;verifyPayPalWebhook&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;./paypal-verify&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;POST&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Request&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;headers&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;paypal-transmission-id&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;paypal-transmission-id&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;paypal-transmission-time&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;paypal-transmission-time&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;paypal-transmission-sig&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;paypal-transmission-sig&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;paypal-cert-url&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;paypal-cert-url&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;paypal-auth-algo&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;paypal-auth-algo&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;body&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;text&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;valid&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;verifyPayPalWebhook&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
&lt;span class="nx"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="na"&gt;webhookId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;PAYPAL_WEBHOOK_ID&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;valid&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Response&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;invalid&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;status&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;400&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="c1"&gt;// ... process&lt;/span&gt;
&lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Response&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;ok&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Use PayPal's official SDK if at all possible — implementing the verification yourself is error-prone.&lt;/p&gt;

&lt;h1&gt;
  
  
  Common Stripe Webhook Bugs to Test For
&lt;/h1&gt;

&lt;p&gt;Missing idempotency. Same payment_intent.succeeded arrives twice, you mark the order paid twice and ship twice.&lt;br&gt;
Out-of-order events. charge.refunded arrives before charge.succeeded. Your handler crashes on missing parent.&lt;br&gt;
Test events processed in production. Always check event.livemode.&lt;br&gt;
Body parsed before signature verified. Signature fails 100% of the time.&lt;br&gt;
Slow 2xx. Stripe retries; you process duplicates.&lt;br&gt;
Treating payment_intent.processing as success. It's not. Wait for succeeded.&lt;br&gt;
Refund webhooks not handled. Customer disputes a charge, your DB still shows it paid.&lt;/p&gt;

&lt;h1&gt;
  
  
  Common PayPal Webhook Bugs
&lt;/h1&gt;

&lt;p&gt;Signature verification skipped because "it's hard." Don't. Use the SDK.&lt;br&gt;
Event types not mapped to product events. PayPal sends PAYMENT.CAPTURE.COMPLETED for what Stripe calls payment_intent.succeeded. Different vocabulary, same concept.&lt;br&gt;
Sandbox vs live confusion. Both modes use similar URLs; check the credentials, not the URL.&lt;br&gt;
Multiple webhook subscriptions firing same event twice. Audit your subscription list.&lt;/p&gt;

&lt;h1&gt;
  
  
  What to Test Manually
&lt;/h1&gt;

&lt;p&gt;Some tests are easier to run manually in test mode than to automate:&lt;/p&gt;

&lt;p&gt;Card decline flows. Trigger with Stripe's 4000000000000002 test card.&lt;br&gt;
3DS / SCA flows. Trigger with 4000002500003155.&lt;br&gt;
Dispute creation. Stripe lets you simulate disputes in the dashboard.&lt;br&gt;
PayPal sandbox checkout. Use sandbox buyer accounts.&lt;br&gt;
For each, capture the full webhook sequence in YoBox and turn into a test fixture.&lt;/p&gt;

&lt;h1&gt;
  
  
  End-to-End with Email
&lt;/h1&gt;

&lt;p&gt;Most payment flows also send an email to the customer (receipt, confirmation). To test the full flow:&lt;/p&gt;

&lt;p&gt;Sign up a test user with a YoBox Temp Mail address.&lt;br&gt;
Trigger payment in Stripe test mode.&lt;br&gt;
Wait for the receipt email in the temp inbox.&lt;br&gt;
Wait for the webhook to fire (capture in YoBox Webhook Tester or your real handler).&lt;br&gt;
Assert on database state, email body, and webhook payload.&lt;br&gt;
See "Email Testing Guide for Developers" and "Webhook Testing Complete Guide" for the patterns.&lt;/p&gt;

&lt;h1&gt;
  
  
  FAQ
&lt;/h1&gt;

&lt;p&gt;Can I test Stripe webhooks without ngrok?&lt;br&gt;
Yes — use the Stripe CLI's stripe listen for local, or deploy to a staging URL. For inspection, YoBox Webhook Tester.&lt;/p&gt;

&lt;p&gt;Why is my Stripe signature always failing?&lt;br&gt;
99% of the time: body is being parsed before verification. Use raw body.&lt;/p&gt;

&lt;p&gt;Does PayPal sign webhooks?&lt;br&gt;
Yes — it's just more complex than Stripe's HMAC. Use the official SDK.&lt;/p&gt;

&lt;p&gt;Should I unit test or integration test payment webhooks?&lt;br&gt;
Both. Unit tests with fixtures for handler logic; integration tests in Stripe test mode for the full flow.&lt;/p&gt;

&lt;p&gt;Can I replay a Stripe webhook?&lt;br&gt;
Yes — from the Stripe dashboard webhook log, click "Resend." Useful for replaying after fixing handler bugs.&lt;/p&gt;

&lt;h1&gt;
  
  
  Bottom Line
&lt;/h1&gt;

&lt;p&gt;Payment webhooks deserve more test coverage than they usually get. Inspect the real payloads with YoBox Webhook Tester, save fixtures, unit test the handler, end-to-end test in provider test mode, and never skip signature verification or idempotency. The cost of getting this right once is way lower than the cost of refunding 1,000 customers because of a duplicate-event bug.&lt;/p&gt;

&lt;h1&gt;
  
  
  YoBox Team
&lt;/h1&gt;

&lt;p&gt;Builder behind YoBox — a privacy-first toolbox for developers and QA engineers covering disposable email, webhook capture, regex, secure passwords, Docker, and end-to-end testing.&lt;/p&gt;

</description>
      <category>stripe</category>
      <category>paypal</category>
      <category>webhooks</category>
      <category>api</category>
    </item>
  </channel>
</rss>
