Etsy v3 API has no customer messaging endpoint — there is no `sendMessage` call. Instead, use the `order.paid` webhook (released late 2025) to detect new orders in real time, fetch order details via `GET /shops/{shop_id}/receipts/{receipt_id}`, then send personalized post-purchase emails through an external ESP (SendGrid, Resend). Every API request requires both `Authorization: Bearer <token>` AND `x-api-key: <keystring>` headers. Rate limit: 10 req/sec, 10,000 req/day.
API Quick Reference
OAuth 2.0 with PKCE
10 req/sec, 10,000 req/day
JSON
REST only
Understanding Etsy Customer Communication via the API
The single most important fact about Etsy customer messaging via API: there is no messaging API endpoint. Etsy v3 does not expose shop conversations or the ability to send messages through the API. The Messages/Conversations section you see in the Etsy seller dashboard is entirely internal and not accessible programmatically.
What you can do is build a post-purchase communication system that operates outside Etsy's messaging system. The architecture: subscribe to Etsy's `order.paid` webhook (added late 2025), receive a notification when an order is paid, fetch the order details including buyer email via `getShopReceipts`, and send personalized communications via an external ESP. This replicates the effect of an Etsy message without using Etsy's messaging system.
This approach is the industry-standard workaround used by high-volume Etsy sellers. The buyer email is available via `transactions_r` scope, but `buyer_email` access requires a separate Etsy approval in addition to the standard scope. See https://developers.etsy.com/documentation/ for full API reference. Every request must include both `Authorization: Bearer <access_token>` and `x-api-key: <keystring>` headers — omitting either returns 401.
https://api.etsy.com/v3/applicationSetting Up Etsy API Authentication (OAuth 2.0 + PKCE)
Etsy mandates PKCE on every OAuth 2.0 authorization flow — it is not optional as with some other platforms. Every API request requires both the `Authorization: Bearer <access_token>` header AND the `x-api-key: <keystring>` header. Access tokens expire in 1 hour; you must use the refresh token to mint new access tokens. Apps inactive for 6 months are banned.
- 1Register your app at https://www.etsy.com/developers/your-apps — enable 2FA on your Etsy account first.
- 2Note your app's keystring (client ID) and secret from the app settings.
- 3Request 'transactions_r' scope; also request 'buyer_email' access separately if you need the buyer's email address.
- 4Build the PKCE flow: generate a 43-128 character code verifier from [A-Za-z0-9._~-], compute code_challenge = base64url(SHA256(verifier)).
- 5Redirect user to: https://www.etsy.com/oauth/connect?response_type=code&client_id={keystring}&redirect_uri={url}&scope=transactions_r&state={random}&code_challenge={challenge}&code_challenge_method=S256
- 6After redirect, exchange the code for tokens at POST https://api.etsy.com/v3/public/oauth/token with code_verifier included.
- 7Store the access_token (1 hour) and refresh_token (90 days) securely.
- 8For commercial use across multiple sellers' shops, submit a 'Request Commercial Access' application in the developer portal.
1import os, secrets, hashlib, base64, requests23KEYSTRING = os.environ['ETSY_KEYSTRING']4SECRET = os.environ['ETSY_SECRET']5REDIRECT_URI = os.environ['ETSY_REDIRECT_URI']67# Step 1: Generate PKCE code verifier and challenge8code_verifier = base64.urlsafe_b64encode(secrets.token_bytes(32)).decode().rstrip('=')9code_challenge = base64.urlsafe_b64encode(10 hashlib.sha256(code_verifier.encode()).digest()11).decode().rstrip('=')1213# Step 2: Build authorization URL14auth_url = (f'https://www.etsy.com/oauth/connect?'15 f'response_type=code&client_id={KEYSTRING}'16 f'&redirect_uri={REDIRECT_URI}&scope=transactions_r'17 f'&state={secrets.token_hex(16)}'18 f'&code_challenge={code_challenge}&code_challenge_method=S256')19print(f'Visit: {auth_url}')2021# Step 3: Exchange code for tokens (after redirect)22def exchange_code(auth_code, verifier):23 resp = requests.post('https://api.etsy.com/v3/public/oauth/token', data={24 'grant_type': 'authorization_code',25 'client_id': KEYSTRING,26 'redirect_uri': REDIRECT_URI,27 'code': auth_code,28 'code_verifier': verifier29 })30 resp.raise_for_status()31 return resp.json() # {access_token, refresh_token, token_type, expires_in}3233def refresh_access_token(refresh_token):34 resp = requests.post('https://api.etsy.com/v3/public/oauth/token', data={35 'grant_type': 'refresh_token',36 'client_id': KEYSTRING,37 'refresh_token': refresh_token38 })39 resp.raise_for_status()40 return resp.json()Security notes
- •Store code_verifier securely for the OAuth callback — it must match the code_challenge used in the authorization URL.
- •NEVER expose the client secret in frontend code — all token exchanges happen server-side.
- •Access tokens expire after 1 hour — implement automatic refresh using the refresh_token before expiry.
- •If a user revokes app access, the refresh_token stops working — handle invalid_grant errors by triggering re-authorization.
- •Store refresh tokens encrypted at rest — they provide 90-day access to the user's Etsy account.
- •The x-api-key header must be included on every request alongside the Bearer token — missing it returns 401.
Key endpoints
/shops/{shop_id}/receiptsFetch shop orders (receipts) with optional filters. Use `was_paid=true` to get only paid orders. Returns buyer info, line items, and totals.
| Parameter | Type | Required | Description |
|---|---|---|---|
shop_id | number | required | Your Etsy shop ID |
was_paid | boolean | optional | Filter to paid orders only |
min_created | number | optional | Unix timestamp — only receipts created after this time |
limit | number | optional | Results per page (default 25, max 100) |
Request
1GET https://api.etsy.com/v3/application/shops/12345678/receipts?was_paid=true&min_created=1714000000&limit=25Response
1{"count": 1, "results": [{"receipt_id": 9876543, "buyer_user_id": 44556677, "buyer_email": "buyer@example.com", "name": "Jane Smith", "status": "paid", "total_price": {"amount": 2999, "divisor": 100, "currency_code": "USD"}, "transactions": [{"title": "Handmade Ring", "quantity": 1}], "message_from_buyer": "No rush!"}]}/shops/{shop_id}/receipts/{receipt_id}Fetch a single order receipt by ID. Use after receiving an order.paid webhook to get full order details.
| Parameter | Type | Required | Description |
|---|---|---|---|
shop_id | number | required | Your Etsy shop ID |
receipt_id | number | required | The receipt ID from the webhook payload |
Request
1GET https://api.etsy.com/v3/application/shops/12345678/receipts/9876543Response
1{"receipt_id": 9876543, "buyer_email": "buyer@example.com", "name": "Jane Smith", "first_line": "123 Main St", "city": "Portland", "state": "OR", "zip": "97201", "country_iso": "US", "status": "paid", "total_price": {"amount": 2999, "divisor": 100, "currency_code": "USD"}, "transactions": [{"title": "Handmade Ring", "quantity": 1, "price": {"amount": 2999, "divisor": 100}}], "message_from_buyer": "No rush!"}Webhook subscriptionSubscribe to the order.paid event via the Etsy Developer Portal webhook management UI. Webhook payloads are minimal — you still need to call getShopReceipts to get order details.
| Parameter | Type | Required | Description |
|---|---|---|---|
event | string | required | order.paid or order.canceled |
url | string | required | HTTPS callback URL for webhook delivery |
Request
1Configure via Etsy Developer Portal at https://www.etsy.com/developers/your-apps → select app → Webhooks tabResponse
1{"webhook_id": "evt_abc123", "shop_id": 12345678, "fetch_url": "https://api.etsy.com/v3/application/shops/12345678/receipts/9876543"}Step-by-step automation
Set Up OAuth 2.0 with PKCE Authentication
Why: Etsy's PKCE requirement is non-negotiable — every authorization flow must include a code_challenge or the token exchange returns invalid_request.
Generate a cryptographically random code verifier (43-128 chars, URL-safe base64), compute SHA256 hash for the code challenge, redirect to Etsy's authorization endpoint, and exchange the returned code for access and refresh tokens. Store both tokens — access tokens expire in 1 hour, so implement automatic refresh.
1# Exchange authorization code for tokens (after OAuth redirect)2curl -X POST 'https://api.etsy.com/v3/public/oauth/token' \3 -d 'grant_type=authorization_code' \4 -d 'client_id=your_keystring' \5 -d 'redirect_uri=https://yourapp.com/oauth/callback' \6 -d 'code=auth_code_from_redirect' \7 -d 'code_verifier=your_pkce_verifier'Pro tip: The most common Etsy API bug is missing the `x-api-key` header — even authenticated requests need it alongside the Bearer token. Build it into every request helper from day one.
Expected result: A `getValidToken()` function that automatically refreshes the access token when needed, plus an `etsyGet()` helper that includes both required headers.
Register and Handle order.paid Webhook
Why: Etsy added webhooks in late 2025 after years of being polling-only — this is now the real-time signal you need instead of polling receipts every 5 minutes.
Register the `order.paid` webhook in the Etsy Developer Portal UI. When an order is paid, Etsy sends a minimal payload with the shop ID and a fetch URL pointing to the receipt. Verify the webhook signature using the `whsec_` prefixed secret, then call the fetch URL to get full order details.
1# Webhook verification: build signed_content = webhook-id + "." + webhook-timestamp + "." + raw_body2# Decode whsec_ secret, compute HMAC-SHA256, compare base64 to webhook-signature header3# No direct cURL example — register webhook via the Developer Portal UIPro tip: The Etsy order.paid webhook payload is intentionally minimal — it gives you a `fetch_url` pointing to the receipt. You still need to call the receipts endpoint to get buyer details, line items, and totals.
Expected result: Your endpoint receives real-time POST requests within seconds of each order payment. Returns 200 immediately, processes order asynchronously.
Fetch Order Details and Send Post-Purchase Email
Why: The webhook payload doesn't contain order details — you must call getShopReceipts to get buyer email, items ordered, and totals needed to personalize the post-purchase message.
Use the `fetch_url` from the webhook payload (or call `GET /shops/{shop_id}/receipts/{receipt_id}` directly) to get full order details. Extract buyer email (requires buyer_email permission), item titles, and total. Divide monetary values by the `divisor` field. Then send via your ESP.
1# Fetch single receipt2curl -X GET 'https://api.etsy.com/v3/application/shops/12345678/receipts/9876543' \3 -H 'Authorization: Bearer your_access_token' \4 -H 'x-api-key: your_keystring'Pro tip: Etsy prices are in sub-units — `amount: 2999, divisor: 100` = $29.99. NEVER display the raw `amount` value. Always divide by `divisor`. This is the most common Etsy API bug that causes 100x price display errors.
Expected result: Buyer receives a personalized post-purchase email with their order items, total, and any message they included with the order.
Fallback: Poll Recent Orders When Webhooks Are Unavailable
Why: The Etsy webhook infrastructure is new (late 2025) and may not yet be available to all apps — polling is the fallback for apps without webhook access.
If webhooks aren't available, poll `getShopReceipts` every 5 minutes using `min_created` timestamp to fetch only new orders since your last check. Store the last processed receipt ID or timestamp in your database to avoid sending duplicate emails.
1# Poll for orders created in the last 5 minutes2TIMESTAMP=$(date -v-5M +%s) # 5 minutes ago (macOS)3curl -X GET "https://api.etsy.com/v3/application/shops/12345678/receipts?was_paid=true&min_created=${TIMESTAMP}" \4 -H 'Authorization: Bearer your_access_token' \5 -H 'x-api-key: your_keystring'Pro tip: Polling every 5 minutes uses 288 API calls per day out of your 10,000 daily quota. That's only 2.88% — fine for most shops. Reduce to every 1 minute during peak hours and 15 minutes overnight to match your actual order velocity.
Expected result: Every 5 minutes, checks for new orders and sends post-purchase emails for any that haven't been processed yet.
Complete working code
This complete webhook handler receives Etsy order.paid events, verifies the signature, fetches order details from the receipts API, and sends a personalized post-purchase email via SendGrid — replicating the effect of an Etsy message without the non-existent messaging API.
1import os, hmac, hashlib, base64, time, logging, requests2from flask import Flask, request, abort34logging.basicConfig(level=logging.INFO)5app = Flask(__name__)67KEYSTRING = os.environ['ETSY_KEYSTRING']8WEBHOOK_SECRET = os.environ['ETSY_WEBHOOK_SECRET']9REFRESH_TOKEN = os.environ['ETSY_REFRESH_TOKEN']10SENDGRID_KEY = os.environ['SENDGRID_API_KEY']11TEMPLATE_ID = os.environ['SENDGRID_TEMPLATE_ID']1213_token_cache = {'token': None, 'expires_at': 0, 'refresh': REFRESH_TOKEN}1415def get_token():16 if time.time() < _token_cache['expires_at'] - 60:17 return _token_cache['token']18 resp = requests.post('https://api.etsy.com/v3/public/oauth/token',19 data={'grant_type': 'refresh_token', 'client_id': KEYSTRING, 'refresh_token': _token_cache['refresh']})20 resp.raise_for_status()21 d = resp.json()22 _token_cache.update({'token': d['access_token'], 'expires_at': time.time() + d['expires_in']})23 return d['access_token']2425def etsy_get(path, params=None):26 resp = requests.get(f'https://api.etsy.com/v3/application{path}',27 headers={'Authorization': f'Bearer {get_token()}', 'x-api-key': KEYSTRING}, params=params)28 resp.raise_for_status()29 return resp.json()3031def verify_webhook(raw_body, headers):32 wid = headers.get('webhook-id', '')33 wts = headers.get('webhook-timestamp', '')34 wsig = headers.get('webhook-signature', '')35 if abs(time.time() - int(wts)) > 300:36 return False37 signed = f'{wid}.{wts}.{raw_body.decode()}'38 secret = base64.b64decode(WEBHOOK_SECRET.replace('whsec_', ''))39 expected = base64.b64encode(hmac.new(secret, signed.encode(), hashlib.sha256).digest()).decode()40 return any(hmac.compare_digest(expected, s.strip()) for s in wsig.split())4142def format_price(p):43 return p['amount'] / p['divisor']4445@app.route('/webhooks/etsy-order', methods=['POST'])46def order_paid():47 raw = request.get_data()48 if not verify_webhook(raw, request.headers):49 abort(401)50 payload = request.get_json()51 shop_id = payload.get('shop_id')52 fetch_url = payload.get('fetch_url', '')53 receipt_id = fetch_url.rstrip('/').split('/')[-1]54 if not receipt_id:55 return '', 20056 try:57 receipt = etsy_get(f'/shops/{shop_id}/receipts/{receipt_id}')58 email = receipt.get('buyer_email')59 if not email:60 logging.info(f'No buyer email for receipt {receipt_id}')61 return '', 20062 items = [{'title': t['title'], 'qty': t['quantity'], 'price': format_price(t['price'])} for t in receipt.get('transactions', [])]63 data = {'buyer_name': receipt.get('name', 'there'), 'total': format_price(receipt['total_price']), 'items': items}64 sg_payload = {'from': {'email': 'orders@yourshop.com'}, 'personalizations': [{'to': [{'email': email}], 'dynamic_template_data': data}], 'template_id': TEMPLATE_ID}65 sg_resp = requests.post('https://api.sendgrid.com/v3/mail/send', json=sg_payload, headers={'Authorization': f'Bearer {SENDGRID_KEY}'})66 logging.info(f'Email sent to {email}: {sg_resp.status_code}')67 except Exception as e:68 logging.error(f'Error processing receipt {receipt_id}: {e}')69 return '', 2007071if __name__ == '__main__':72 app.run(port=3000)Error handling
Unauthorized — missing or invalid credentialsEither the access token has expired (1 hour TTL), the x-api-key header is missing, or both headers are present but the token was issued for a different keystring.
Implement automatic token refresh. Always include BOTH headers: `Authorization: Bearer <token>` AND `x-api-key: <keystring>`. Verify the access token was issued for the same keystring used in x-api-key.
Refresh the access token using the refresh token, then retry the request once.
Forbidden — scope not authorizedMissing transactions_r scope for receipts access, or buyer_email access not separately approved for buyer email addresses.
Ensure transactions_r is in the scopes list during OAuth authorization. For buyer_email, submit a separate access request via the Etsy developer portal — it requires additional approval.
No retry — re-authorize with correct scopes.
Rate limit exceededHit the 10 req/sec or 10,000 req/day cap. Sliding window means daily quota doesn't fully reset at midnight.
Add 150ms delays between requests when processing high-volume order batches. Cache receipt data to avoid redundant fetches. Newly approved apps have lower limits (5/sec, 5,000/day) — email developers@etsy.com for increases.
Exponential backoff starting at 200ms. Check Etsy's rate-limit headers for remaining quota.
OAuth token exchange: invalid_grantThe authorization code was already used (each code is single-use), or the refresh token was revoked because the user removed the app.
Authorization codes: ensure you're exchanging immediately after the redirect and not reusing. Revoked refresh tokens: re-initiate the OAuth flow for the affected user.
No retry — initiate fresh OAuth flow.
Not found — receipt does not existThe receipt ID extracted from the webhook fetch_url doesn't exist for the given shop_id, or the shop_id in the request doesn't match the authenticated user's shop.
Use the complete fetch_url from the webhook payload rather than constructing the URL manually. Verify that the OAuth token belongs to the shop matching shop_id.
No retry — verify the shop association and receipt ID.
Rate Limits for Etsy API
| Scope | Limit | Window |
|---|---|---|
| Per app — requests per second | 10 req/sec | New apps: 5 req/sec until commercially approved |
| Per app — requests per day | 10,000 req/day | Sliding 24-hour window (not midnight reset) |
| Access token lifetime | 3,600 seconds (1 hour) | Per token; refresh with refresh_token |
| Refresh token lifetime | 90 days | Revoked on user app removal |
1import time23def etsy_get_with_retry(path, params=None, max_retries=4):4 delay = 0.25 for attempt in range(max_retries):6 resp = requests.get(7 f'https://api.etsy.com/v3/application{path}',8 headers={'Authorization': f'Bearer {get_token()}', 'x-api-key': KEYSTRING},9 params=params10 )11 if resp.status_code == 429:12 print(f'Rate limited (attempt {attempt+1}), waiting {delay}s')13 time.sleep(delay)14 delay = min(delay * 2, 10)15 continue16 if resp.status_code == 401:17 # Token expired — refresh and retry once18 _token_cache['expires_at'] = 019 continue20 resp.raise_for_status()21 return resp.json()22 raise RuntimeError('Etsy API retries exhausted')- Add 150ms delays between batch requests to stay safely under the 10 req/sec limit.
- Cache receipt data for 30 minutes — don't re-fetch the same receipt for multi-step email sequences.
- Use the order.paid webhook instead of polling — it uses zero daily quota for event detection.
- For polling fallback, check once every 5 minutes (288 calls/day) rather than every minute.
- Email developers@etsy.com with your app's use case to request higher rate limits once you're operational.
Security checklist
- Include both Authorization: Bearer and x-api-key headers on every Etsy API request — missing either returns 401.
- Verify Etsy webhook signatures using the whsec_ format: strip prefix, base64-decode secret, compute HMAC-SHA256 over webhook-id.webhook-timestamp.raw_body.
- Implement replay protection by rejecting webhooks where |now - webhook-timestamp| > 300 seconds.
- Store refresh tokens encrypted at rest — they provide 90-day access to the seller's Etsy account.
- Never expose the OAuth client secret in frontend or mobile code — token exchanges are server-side only.
- Handle buyer email as PII — don't log it in plain text, apply appropriate data retention policies.
- Implement token rotation monitoring — alert when refresh tokens are within 7 days of expiry (90 days).
- Audit your app's API usage — an inactive app (no requests in 6 months) gets banned by Etsy.
Automation use cases
Order Confirmation with Custom Message
intermediateSend a personalized thank-you email immediately after order payment, including items ordered and a custom message about processing time.
Shipping Notification
intermediateSend a shipping confirmation email when you add tracking to the Etsy order, pairing tracking upload with email notification.
Review Request Sequence
advanced7 days after estimated delivery, send an automated review request email with a direct link to the Etsy review page.
VIP Customer Recognition
advancedQuery order history to identify repeat buyers and send personalized loyalty discounts outside Etsy's system.
No-code alternatives
Don't want to write code? These platforms can automate the same workflows visually.
Zapier
Free tier available; Starter $19.99/monthZapier's Etsy integration triggers on new orders and can send Mailchimp or Gmail emails with order details.
- + Zero code
- + Native Etsy + Mailchimp integration
- + Easy setup
- - No webhook support — polls Etsy every few minutes
- - Limited order data access
- - Can't access buyer_email directly
Make (formerly Integromat)
Free tier (1,000 ops/month); Core $9/monthMake supports Etsy as a trigger and can route order data to SendGrid or Gmail for post-purchase emails.
- + More affordable than Zapier
- + Better data transformation
- + Conditional logic
- - Polling-based, not real-time
- - Limited Etsy scope support
- - Complex for multi-step flows
n8n
Self-hosted free; Cloud Starter €20/monthn8n can poll Etsy's receipts endpoint via HTTP Request nodes and trigger email sends through SendGrid or Resend.
- + Self-hosted free
- + Full HTTP request control for custom headers
- + Cron scheduling
- - No native Etsy node
- - Must manually implement PKCE auth
- - Complex setup
Best practices
- Accept that there is no Etsy messaging API — design your workflow around external email delivery from the start.
- Always include both Authorization: Bearer and x-api-key headers on every request — missing x-api-key is the most common integration failure.
- Implement automatic token refresh: access tokens expire in 1 hour, so build refresh logic into your API wrapper before hitting the first expiry.
- Always divide Etsy monetary values by their divisor — `amount: 2999, divisor: 100` = $29.99. Never display raw amounts.
- Use the order.paid webhook (2025 addition) instead of polling receipts — webhooks are real-time and consume zero daily API quota.
- Store processed receipt IDs to prevent sending duplicate emails — webhooks can be delivered more than once.
- Request Commercial access early if your app will serve multiple sellers — the review takes days to weeks and is the critical path.
- Apply for buyer_email permission separately — without it, receipt data contains buyer user IDs but not email addresses.
Ask AI to help
Copy one of these prompts to get a personalized, working implementation.
I'm building an Etsy post-purchase email automation using the v3 API. I understand there's no messaging API, so I'm using the order.paid webhook + getShopReceipts + SendGrid. My current issue is: [describe problem]. I'm implementing PKCE OAuth and including both Authorization: Bearer and x-api-key headers. Here's my webhook verification code: [paste code]. The error I'm seeing is: [paste error]. Help me fix this, specifically around the webhook signature verification (whsec_ format) and the token refresh flow.
Build an Etsy order management dashboard that shows recent orders fetched from the Etsy API (via Supabase Edge Function with PKCE OAuth), displays buyer name, items ordered, and total (formatted using the divisor), and has a button to trigger a post-purchase email via SendGrid for each order. Show email send status per order. Store order data and email status in Supabase. Use shadcn/ui and Tailwind CSS.
Frequently asked questions
Does the Etsy API have an endpoint to send messages to buyers?
No. Etsy v3 API has no messaging or conversation endpoint. Etsy's internal messaging system is completely separate from the public API and is not accessible programmatically. The workaround is to use the order.paid webhook to detect new orders, fetch buyer details via getShopReceipts (including email if you have buyer_email permission), and send communications through an external ESP like SendGrid or Resend. This produces the same result as an Etsy message but delivered via email.
Why does every Etsy API request need two authentication headers?
Etsy's v3 API requires both `Authorization: Bearer <access_token>` (the OAuth 2.0 token proving the user authorized your app) AND `x-api-key: <keystring>` (your app's API key identifying which application is making the call). Omitting either header returns 401. This is non-standard but required by Etsy's architecture. Build both into every API request wrapper from the start.
What is PKCE and why does Etsy require it?
PKCE (Proof Key for Code Exchange, RFC 7636) is an OAuth 2.0 security extension that prevents authorization code interception attacks. You generate a random code_verifier (43-128 chars), compute code_challenge = base64url(SHA256(verifier)), include the challenge in the authorization URL, and include the original verifier when exchanging the code for tokens. Etsy makes PKCE mandatory on every authorization flow — unlike Google or GitHub where it's optional.
How do I get the buyer's email address from Etsy orders?
Buyer email access requires two things: the `transactions_r` scope AND a separate `buyer_email` permission approval from Etsy. With only `transactions_r`, you get the buyer's user ID but not their email. To request buyer_email access, submit an application through the Etsy Developer Portal explaining your use case. Until approved, you can use the buyer's name from the receipt for personalization but cannot email them directly.
What is the Etsy webhook payload structure for order.paid?
Etsy's order.paid webhook payload is intentionally minimal: `{ shop_id: 12345678, fetch_url: 'https://api.etsy.com/v3/application/shops/12345678/receipts/9876543' }`. You receive the shop ID and a pre-built URL pointing to the receipt — you still need to make a separate API call to get buyer details, line items, and totals. The webhook reduces but does not eliminate API calls.
Is the Etsy API free?
The Etsy API is free for personal access (up to 5 shops you own or that grant you access). For commercial use serving arbitrary sellers' shops, you need to request 'Commercial Access' which is manually reviewed. Commercial access is free — there are no usage fees — but new apps start at lower rate limits (5 req/sec, 5,000/day) until approved for the full 10 req/sec / 10,000/day tier.
Can RapidDev help build a custom Etsy post-purchase communication system?
Yes. RapidDev has built 600+ apps including Etsy seller tools that handle the full PKCE OAuth flow, webhook verification, receipt data processing, and ESP integration. We navigate the buyer_email permission process and build the polling fallback for pre-webhook compatibility. Book a free consultation at rapidevelopers.com.
Need this automated?
Our team has built 600+ apps with API automations. We can build this for you.
Book a free consultation