Section 03 of 14
Webhook Intake
What This Does
When Shopify creates or updates an order, it sends a webhook (an HTTP request) to our app. This is the front door — the very first thing that happens. The webhook handler validates the payload, checks if the order qualifies for processing (international? has order-level discount? not already processed?), and if so, creates a record in our database and drops it into the SQS queue for processing.
Why It Matters
This is the gatekeeper. If the qualification logic is wrong, we'd either process orders that don't need correction (wasting the merchant's plan quota) or miss orders that DO need correction (defeating the whole purpose of the app). Every order enters through this single entry point.
How It Works
- 1.Shopify sends a POST request to /webhooks/orders with the order data in the body.
- 2.Shopify's built-in authentication verifies the HMAC signature (prevents fake webhooks).
- 3.We validate the payload has the required fields (admin_graphql_api_id).
- 4.We look up the shop in our database. If we don't recognize them, we silently return 200 OK.
- 5.FIRST CHECK: Is ShipStation connected? If not, we skip and potentially send a 'please connect ShipStation' reminder email.
- 6.SECOND CHECK: Qualification — is this order international? Does it have ORDER-LEVEL discounts? Is it already being processed?
- 7.If it qualifies, we upsert a ProcessedOrder record (creates new or resets existing) and enqueue it to SQS.
- 8.We ALWAYS return 200 OK to Shopify, even on errors. If we returned 500, Shopify would keep retrying the webhook forever.
The Code
Key Decisions
- ●We return 200 OK even on errors because Shopify retries failed webhooks aggressively. It's better to log the error and handle it ourselves than to get hammered with retries.
- ●The qualification check for order-level discounts looks at allocation_method='across' and target_selection='all'. This is how Shopify distinguishes order-wide discounts from line-item-specific ones.
- ●We use upsert (create-or-update) for the ProcessedOrder so that if Shopify sends the same webhook twice (which happens), we don't create duplicate records.
- ●The BLOCKED_STATUSES set prevents re-enqueuing orders that are already being processed or have completed. Only PENDING, FAILED, SKIPPED, and REJECTED orders can be re-enqueued.
What Could Go Wrong
- ●If our HMAC verification failed, anyone could send fake webhooks. Shopify's authenticate.webhook() handles this for us.
- ●If we accidentally qualified domestic orders, we'd waste processing quota. The isInternational() check prevents this.
- ●If we returned 500 on errors, Shopify would retry the webhook up to 19 times over 48 hours, flooding us with duplicate work.