Use the Etsy v3 API order.paid webhook to trigger fulfillment. Fetch order details with getShopReceipts, ship via your 3PL, then call POST /shops/{shop_id}/receipts/{receipt_id}/tracking to add a tracking number. Etsy notifies the buyer automatically once tracking is attached.
| Fact | Value |
|---|---|
| Platform | Etsy |
| Auth method | OAuth 2.0 with mandatory PKCE |
| Rate limits | 10 req/sec, 10,000 req/day (new apps: 5/sec, 5,000/day) |
| Difficulty | Intermediate |
| Time required | 45–60 minutes |
| Last updated | May 2026 |
API Quick Reference
OAuth 2.0 with mandatory PKCE
10 req/sec, 10,000 req/day (new apps: 5/sec, 5,000/day)
JSON
REST only
API overview
https://openapi.etsy.com/v3/applicationAuthentication
Etsy mandates PKCE (Proof Key for Code Exchange, RFC 7636) for all OAuth flows. Every API request must include both the Authorization: Bearer header and the x-api-key header. Access tokens expire after 1 hour; use the refresh token (valid 90 days) to get a new one without user re-authentication.
Key endpoints
/shops/{shop_id}/receiptsRetrieves a paginated list of shop receipts. Filter by was_paid=true and was_shipped=false to get orders awaiting fulfillment.
| Parameter | Type | Required | Description |
|---|---|---|---|
| optional | ||
| optional | ||
| optional | ||
| optional | ||
| optional |
/shops/{shop_id}/receipts/{receipt_id}Fetches full details for one order including line items, shipping address, and buyer contact info.
| Parameter | Type | Required | Description |
|---|---|---|---|
| optional | ||
| optional |
/shops/{shop_id}/receipts/{receipt_id}/trackingAttaches a tracking number to an order and marks it as shipped. Triggers Etsy's automated buyer notification email.
| Parameter | Type | Required | Description |
|---|---|---|---|
| optional | ||
| optional | ||
| optional | ||
| optional | ||
| optional |
/shops/{shop_id}/receipts/{receipt_id}Updates order status fields. Use was_shipped=true to mark as fulfilled when tracking is added externally or when no tracking is available.
| Parameter | Type | Required | Description |
|---|---|---|---|
| optional | ||
| optional | ||
| optional | ||
| optional |
Step-by-step automation
Set Up Webhook for order.paid Events
Register a webhook on the order.paid event so your server is notified the moment a buyer's payment clears. Verify the webhook signature on every incoming request using your shared secret (whsec_ prefix).
1# Register the webhook2curl -X POST https://openapi.etsy.com/v3/application/webhooks \3 -H "Authorization: Bearer $ETSY_ACCESS_TOKEN" \4 -H "x-api-key: $ETSY_KEYSTRING" \5 -H "Content-Type: application/json" \6 -d '{7 "url": "https://yourapp.com/webhooks/etsy",8 "events": ["order.paid"]9 }'Fetch Order Details with getShopReceipts
After receiving the order.paid event, fetch full receipt details to get the shipping address, line items, and buyer contact. Implement automatic token refresh — access tokens expire after 1 hour.
1# Fetch a single receipt by ID2curl -X GET "https://openapi.etsy.com/v3/application/shops/$SHOP_ID/receipts/$RECEIPT_ID" \3 -H "Authorization: Bearer $ETSY_ACCESS_TOKEN" \4 -H "x-api-key: $ETSY_KEYSTRING"56# Or fetch all unfulfilled paid orders7curl -X GET "https://openapi.etsy.com/v3/application/shops/$SHOP_ID/receipts?was_paid=true&was_shipped=false&limit=50" \8 -H "Authorization: Bearer $ETSY_ACCESS_TOKEN" \9 -H "x-api-key: $ETSY_KEYSTRING"Submit Tracking Number to Etsy
After your 3PL or warehouse provides a tracking number, POST it to Etsy. This marks the order as shipped and triggers Etsy's automated buyer notification email. Always pass send_bcc=true to get a copy of the notification.
1curl -X POST "https://openapi.etsy.com/v3/application/shops/$SHOP_ID/receipts/$RECEIPT_ID/tracking" \2 -H "Authorization: Bearer $ETSY_ACCESS_TOKEN" \3 -H "x-api-key: $ETSY_KEYSTRING" \4 -H "Content-Type: application/json" \5 -d '{6 "tracking_code": "9400111899223408865834",7 "carrier_name": "usps",8 "send_bcc": true9 }'Process Order Backlog with Rate-Limit-Safe Batching
High-volume shops during sales events can hit the 10 req/sec limit when processing a backlog of orders. Use a queue with a token bucket or simple throttle (100ms minimum between requests) to stay under the limit. Implement exponential backoff for 429 responses.
1# Paginate through all unfulfilled orders, page by page2OFFSET=03LIMIT=504while true; do5 RESPONSE=$(curl -s -X GET \6 "https://openapi.etsy.com/v3/application/shops/$SHOP_ID/receipts?was_paid=true&was_shipped=false&limit=$LIMIT&offset=$OFFSET" \7 -H "Authorization: Bearer $ETSY_ACCESS_TOKEN" \8 -H "x-api-key: $ETSY_KEYSTRING")9 COUNT=$(echo $RESPONSE | python3 -c "import sys,json; print(json.load(sys.stdin)['count'])")10 echo "Processing offset $OFFSET, found $COUNT total"11 # process results...12 OFFSET=$((OFFSET + LIMIT))13 sleep 0.5 # rate limit safety: 2 req/sec max during batch14 [ $OFFSET -ge $COUNT ] && break15doneComplete working code
Full webhook-driven Etsy fulfillment automation. Receives order.paid events, fetches order details, and submits tracking after 3PL ships.
Error handling
Access token expired (1-hour TTL) or missing x-api-key header on the request.
Always include both Authorization: Bearer {token} and x-api-key: {keystring} headers. Implement automatic token refresh using the refresh token before the 1-hour expiry. Use the /oauth/token endpoint with grant_type=refresh_token.
A tracking number was already submitted for this receipt ID. Etsy does not allow updating tracking once set.
Catch 409 responses and treat them as success. Add idempotency tracking in your database (store receipt_id + tracking status) to avoid duplicate submission attempts.
Exceeded 10 req/sec or 10,000 req/day limits. New apps have lower limits (5/sec, 5,000/day) and won't be promoted until first production review.
Add a minimum 100ms delay between requests. Use Retry-After header value from the 429 response for backoff. During backlog processing, set delay_between_requests to 150ms or higher to stay safely under the limit.
The receipt_id or shop_id is incorrect, or the receipt belongs to a different shop than the authenticated user.
Verify shop_id from GET /shops/me. Confirm the receipt_id matches an actual order. Log the full URL and both IDs when debugging 404s.
Carrier name string doesn't match Etsy's internal codes. 'USPS', 'United States Postal Service', or 'US Postal Service' all fail — only 'usps' (lowercase) is valid.
Use Etsy's exact carrier codes: usps, fedex, ups, dhl, canada_post, royal_mail, australia_post, deutsche_post, japan_post. Implement a carrier_map dictionary to normalize 3PL carrier strings to Etsy codes.
Using JSON-parsed body instead of the raw request body for HMAC computation, or incorrect webhook secret.
Use express.raw() (not express.json()) for the webhook endpoint so the raw bytes are available. Compute HMAC-SHA256 over the raw body bytes, not the parsed JSON object.
Rate limits & throttling
Security checklist
- Store ETSY_KEYSTRING and tokens in environment variables or a secrets manager — never in source code
- Always verify webhook signatures using HMAC-SHA256 and constant-time comparison (timingSafeEqual)
- Use express.raw() for webhook endpoints so raw bytes are preserved for signature verification
- Refresh tokens expire in 90 days — implement a reminder or auto-refresh flow to prevent customer-facing outages
- Scope tokens to minimum required: transactions_r, shops_r, shops_w — never request write scopes you don't need
- Log receipt IDs and tracking submissions to your database to detect and prevent duplicate submissions
- Use HTTPS for your webhook endpoint — Etsy will not send webhooks to plain HTTP URLs
- Implement a circuit breaker: if 3 consecutive tracking submissions fail, pause the queue and alert
Automation use cases
3PL Integration — Auto-fulfill on Ship Confirmation
Connect order.paid webhook → your 3PL's API to push pick-and-pack jobs, then receive a tracking callback from the 3PL to auto-post tracking to Etsy.
Multi-Carrier Fulfillment
Route different product types to different carriers (e.g., small items via USPS First Class, large items via UPS Ground) and automatically submit the correct tracking code for each.
Backlog Recovery After Peak Sales Events
During Black Friday or Etsy sales promotions, process hundreds of pending orders safely using paginated getShopReceipts with rate-limit-safe batching.
Custom Buyer Notification Emails
Intercept the order.paid event to send a personalized 'your order is being packed' email via SendGrid before Etsy sends the standard shipped notification.
No-code alternatives
Don't want to write code? These platforms can automate the same workflows visually.
Zapier
Etsy + Zapier integration supports new order triggers and can send tracking numbers back via the Etsy app. Limited to Zapier's available actions — custom carrier mapping requires code steps.
Make (Integromat)
More flexible than Zapier for multi-step fulfillment flows. HTTP module can call any Etsy v3 endpoint with custom headers. Supports branching logic for multi-carrier routing.
n8n
Self-hosted option with Etsy OAuth node. Can trigger on order.paid, fetch receipts, call 3PL APIs, and post tracking. Ideal for shops that need full control without SaaS limits.
Best practices
- Always respond 200 OK to the webhook immediately, then process the order asynchronously in a background job
- Store each processed receipt_id with its tracking status in your database to make the flow idempotent
- Use Etsy's carrier code list exactly — lowercase strings only (usps, fedex, ups)
- Implement automatic token refresh before the 1-hour access token expiry to prevent mid-processing failures
- Add 150ms between requests during batch backlog processing to avoid hitting the 10 req/sec limit
- Test webhook signature verification with Etsy's test webhook feature in the developer portal before going live
- Monitor the Etsy developer portal for webhook delivery failures — failed webhooks are retried for 48 hours then silently dropped
- For high-volume shops, queue webhook events in Redis or SQS rather than processing them synchronously in the HTTP handler
Ask AI to help
Copy one of these prompts to get a personalized, working implementation.
I need to automate Etsy order fulfillment using the Etsy v3 API. Help me build a Node.js webhook handler that: (1) verifies the order.paid event signature using HMAC-SHA256 on the raw request body, (2) fetches the receipt details using GET /shops/{shop_id}/receipts/{receipt_id} with both Authorization: Bearer and x-api-key headers, (3) sends the order to a 3PL API, (4) accepts a callback with tracking info and posts it to POST /shops/{shop_id}/receipts/{receipt_id}/tracking. Include automatic OAuth token refresh (1-hour expiry) and 429 rate limit handling.
Build me an Etsy order fulfillment dashboard that: (1) shows all paid, unshipped orders fetched from GET /shops/{shop_id}/receipts?was_paid=true&was_shipped=false, (2) has a form to manually enter a tracking number and carrier for each order, (3) submits tracking via POST /shops/{shop_id}/receipts/{receipt_id}/tracking with the required dual headers (Authorization: Bearer + x-api-key), and (4) marks orders as fulfilled in the UI. Use Supabase to store the OAuth tokens and track fulfillment status.
Frequently asked questions
Does Etsy send the buyer a shipping notification automatically when I add tracking?
Yes. When you POST to /receipts/{receipt_id}/tracking, Etsy automatically sends the buyer an email with the tracking link. Set send_bcc=true to receive a copy. You do not need to send a separate notification unless you want a custom-branded email before Etsy's standard notification.
What carrier names does Etsy accept for the carrier_name field?
Etsy uses lowercase internal codes: usps, fedex, ups, dhl, canada_post, royal_mail, australia_post, deutsche_post, japan_post. Do not use display names like 'United States Postal Service' — they will return a 400 error. Build a mapping dictionary to normalize carrier strings from your 3PL.
Can I update a tracking number after it's been submitted?
No. Once a tracking number is submitted via the API, Etsy returns 409 Conflict on subsequent attempts for the same receipt. If you submitted the wrong tracking number, you need to contact Etsy support to have it removed. This is why idempotency tracking (storing which orders have been submitted) is critical.
The order.paid webhook is listed as added 'late 2025' — what if it doesn't fire?
Implement a polling fallback. Run a scheduled job every 15-30 minutes that calls GET /shops/{shop_id}/receipts?was_paid=true&was_shipped=false and processes any orders not already in your fulfillment database. This ensures no orders are missed if webhook delivery fails.
My shop has 500+ orders from a sale event. How do I process them without getting rate limited?
Use the getShopReceipts endpoint with pagination (limit=50, offset incremented each page) and add a 150ms delay between each API call. At 150ms intervals you'll make ~6 req/sec — well under the 10/sec limit. For very large backlogs, run the processing job overnight in off-peak hours.
Can RapidDev help me connect Etsy fulfillment to my existing 3PL or warehouse system?
Yes. If you need help integrating the Etsy v3 API with a specific 3PL (ShipStation, EasyPost, ShipBob, etc.) or building a custom tracking callback system, RapidDev can build the full integration for you.
Why do I need both Authorization: Bearer and x-api-key headers?
Etsy v3 requires both on every request: Authorization: Bearer {access_token} authenticates the user (your OAuth token), and x-api-key: {keystring} identifies your application. Missing either header returns a 401 error. This is different from most APIs that require only one authentication header.
Need this automated?
Our team has built 600+ apps with API automations. We can build this for you.
Book a free consultation