Medusa Webhook Testing: Complete Guide
How to capture Medusa order, payment, and customer events, verify signatures, and debug your ecommerce integrations without ngrok.
Quick Answer
Medusa webhooks are event-driven: order.placed, order.completed, payment events fire via Redis. Test locally with a free Requex URL (no ngrok needed), inspect raw event payloads, and simulate responses. Medusa has no native webhook UI; use Requex or Svix Play to capture and debug events.
Why Medusa Webhooks Matter for Ecommerce
Medusa is a headless commerce engine built for developers. Unlike Shopify (managed webhooks in Dashboard), Medusa webhooks are triggered through the event system and require you to handle integration endpoints. This is powerful: you control routing, retry logic, and payload transformation. But it also means testing is your responsibility.
Common use cases: syncing orders to ERPs, inventory updates to 3PLs, payment events to accounting systems, and customer events to CRMs. Each requires a reliable webhook endpoint that can verify signatures and handle retries.
Key Medusa Webhook Events
| Event | When It Fires | Use Case |
|---|---|---|
order.placed | Order created | Inventory allocation, fulfillment routing, 3PL integration |
order.completed | Order fulfilled | Accounting sync, shipping label generation, customer email |
order.cancelled | Order cancelled | Inventory restoration, refund processing, supplier notification |
payment.captured | Payment processed | Payment reconciliation, fraud detection, invoice generation |
customer.created | New customer registered | CRM sync (Salesforce, HubSpot), email list, loyalty programs |
Medusa triggers these via the Redis event bus. Your webhook handler must subscribe to the event and POST to your endpoint with the full payload. Unlike Shopify, there's no dashboard UI for testing—you need an external tool.
Testing Medusa Webhooks with Requex (No ngrok)
Medusa webhooks are HTTP POST requests from your backend. To test without deploying, use a temporary endpoint:
- Visit Requex.me and copy your unique webhook URL.
- In your Medusa backend, register the webhook endpoint. Example:
postWebhook('order.placed', 'https://requex-uuid.requex.me') - Trigger an order in your Medusa admin or via API.
- Watch the webhook payload appear in real-time in your Requex dashboard.
- Inspect headers, body, and verify your webhook signature is correct.
Requex doesn't require ngrok, no CLI setup, no exposing localhost directly. The webhook is captured on Requex's server and displayed in your browser instantly.
Common Medusa Webhook Testing Pain Points
Raw Body Handling for Signature Verification
If you're verifying webhook signatures (HMAC), you must capture the raw request body *before* your framework parses it. Medusa sends a signature header; if you parse JSON first and then re-stringify, the bytes won't match.
Fix: Use Express middleware to capture req.rawBody before express.json().
Provider ID Mismatches During Payment Events
Medusa payment events fire asynchronously. Race conditions occur when a Stripe webhook arrives before your Medusa backend has recorded the payment provider ID. Result: payment_intent.succeeded webhook says "payment - id must be defined".
Fix: Ensure payment is created in Medusa *before* Stripe sends webhook. Use idempotency keys on Stripe payment creation.
No Native Webhook Testing UI
Unlike Shopify's "Send test webhook" button, Medusa has no built-in test trigger. You must either:
- Create a real order in the admin (slow)
- Call the API directly (requires auth setup)
- Use a webhook replaying tool (Svix Play, Requex with custom payloads)
Webhook Signature Verification in Medusa
Medusa signs webhooks using HMAC-SHA256 with your webhook signing secret. Verify the signature to ensure the webhook came from your Medusa store:
// Node.js / Express example
const crypto = require('crypto');
app.post('/webhook', express.raw({ type: 'application/json' }), (req, res) => {
const signature = req.headers['x-medusa-signature'];
const secret = process.env.MEDUSA_WEBHOOK_SECRET; // From Medusa admin
const hash = crypto
.createHmac('sha256', secret)
.update(req.body)
.digest('hex');
if (!crypto.timingSafeEqual(Buffer.from(signature), Buffer.from(hash))) {
return res.status(401).json({ error: 'Invalid signature' });
}
const payload = JSON.parse(req.body);
// Process webhook...
res.status(200).json({ received: true });
});Start Testing Webhooks Now
Generate your unique URL and test webhooks instantly. Free, no signup.
Open Webhook Tester →