How to Build a Webhook-Triggered Order Processing Workflow
Process orders automatically when they arrive via webhook.
What This Integration Does
When an order comes in through a storefront, marketplace, or custom checkout, several downstream things usually need to happen at once: the order has to land in the ERP, inventory has to be decremented, the customer needs a confirmation, and the warehouse needs the pick list. Doing this synchronously inside the checkout request is fragile and slow. Doing it in a Spojit webhook-triggered workflow keeps the checkout fast, runs the downstream steps reliably with retries, and gives you a clean audit log of every order that came through.
The pattern is simple: a Webhook trigger receives the order JSON, a Condition validates it, a Parallel node fans the work out into independent branches (ERP, email, inventory), and a Response node closes the loop back to the storefront so it knows the order was accepted. Failures in any branch are caught and routed to a notification or dead-letter queue without rolling back the rest.
Prerequisites
- A storefront or order-source that can send a webhook on new orders - shopify, woocommerce, bigcommerce, or a custom checkout that POSTs JSON.
- An ERP connection - netsuite, an internal API via http, or another business system.
- An email path - resend
send-email, smtpsend-email, or the built-in Send Email node. - A notification channel for failures - typically slack
send-message.
Step 1: Configure the Webhook Trigger
Create the workflow, drop a Trigger node, and set its type to Webhook. Save and copy the generated URL into your storefront's webhook config (in Shopify, under Settings → Notifications → Webhooks, choose the Order created event). If the storefront supports HMAC signing (Shopify does), paste the signing secret onto the trigger config so unauthenticated requests get rejected before they reach your workflow.
Step 2: Validate the Payload
Add a Condition node right after the trigger that checks required fields and basic sanity rules: {{ trigger.body.id }} is non-empty, {{ trigger.body.email }} looks like an email (use validation email for a real check), {{ trigger.body.total }} > 0, and line items aren't empty. Route invalid payloads to a slack send-message alert so they can be investigated rather than silently dropped.
Step 3: Look Up or Create the Customer
Before fan-out, resolve the customer in your ERP so each branch has a consistent ID to reference. A Connector node calling netsuite list-customers filtered by email returns either an existing record or empty; follow with a Condition that calls netsuite create-customer on the empty branch. Extracting this into a Subworkflow (e.g. Shared: Customer Lookup or Create) keeps the main workflow tidy.
Step 4: Fan Out with a Parallel Node
Add a Parallel node with three branches that can run independently:
- ERP branch - netsuite
create-recordfor the sales order, mapping line items, customer ID, totals, and shipping address. - Confirmation branch - resend
send-emailto the customer with the order summary. Use a Transform node to build the HTML body from the trigger payload. - Inventory branch - for each line item, call shopify
adjust-inventory(or your warehouse system's equivalent) to decrement stock. Wrap in a Loop over the line items.
If you also need a warehouse pick list or shipping label, add it as a fourth branch via shipstation create-order or shippit create-order.
Step 5: Handle Branch-Level Failures
Inside each branch, add a Condition right after the main Connector call to check success. On failure: log the input and error to a mongodb insert-documents dead-letter collection and send a slack send-message to the ops channel. Don't fail the whole workflow on a single branch error - the storefront has already accepted the order, and you want the other branches to complete. Keep failure metadata explicit so a later replay workflow can pick up where this one left off.
Step 6: Return a Response to the Storefront
End the workflow with a Response node returning a small JSON acknowledgement to the storefront's webhook call. Status 200 with a body like { "received": true, "internalId": "{{ erp.orderId }}" } is enough - storefronts treat anything else as a failed delivery and may retry. The Response node lets you reply synchronously even though the fan-out work happened first; if you want to ack faster, move the Response above the Parallel node and accept that retries-on-failure handling for the slower steps is on you.
Tips
- Use an idempotency check on the ERP create step - if the storefront retries the webhook, you don't want two orders. Look up by the storefront's order ID before creating.
- Storefronts have webhook delivery timeouts (Shopify is 5 seconds). If your ERP is slow, ack the webhook with Response first, then run the heavy work.
- For high-volume stores, set the trigger's concurrency cap so a burst of orders doesn't fan out into thousands of concurrent ERP calls.
- Use the date connector's
formattool to render order timestamps in the customer's timezone in the confirmation email.
Common Pitfalls
- Duplicate orders on retry - storefronts retry on non-2xx responses, and without an idempotency check you'll create duplicates. Always look up by source order ID before creating.
- Signature mismatch - HMAC validation needs the raw request body, not the parsed JSON. Spojit handles this automatically when configured on the trigger, but a custom storefront may need extra care.
- Inventory negative race conditions - parallel orders for the last item in stock both decrement and now you're oversold. Either gate inventory updates through a serialised Subworkflow or accept the risk and reconcile in a separate sweep.
- Partial failures hidden from the customer - if the ERP failed but the email went out, the customer thinks the order is good. Make the confirmation branch wait until the ERP branch succeeds, or send a different email on partial success.
Testing
Hit the trigger URL with a small representative order payload using curl or the storefront's test-event feature first. Inspect the execution log to see each branch and confirm the ERP record, the confirmation email, and the inventory adjustment all landed as expected. Then deliberately break one branch (point the ERP at an invalid URL temporarily) and confirm the failure path logs to Slack and the dead-letter collection without taking the rest of the workflow down.