Skip to main content
RapidDev - Software Development Agency
API AutomationsEtsyOAuth 2.0

How to Automate Etsy Customer Messages using the API

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.

Need help automating? Talk to an expert
4.9Clutch rating
600+Happy partners
17+Countries served
190+Team members
Advanced8 min read1-2 hoursEtsyMay 2026RapidDev Engineering Team
TL;DR

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

Auth

OAuth 2.0 with PKCE

Rate limit

10 req/sec, 10,000 req/day

Format

JSON

SDK

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.

Base URLhttps://api.etsy.com/v3/application

Setting 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.

  1. 1Register your app at https://www.etsy.com/developers/your-apps — enable 2FA on your Etsy account first.
  2. 2Note your app's keystring (client ID) and secret from the app settings.
  3. 3Request 'transactions_r' scope; also request 'buyer_email' access separately if you need the buyer's email address.
  4. 4Build the PKCE flow: generate a 43-128 character code verifier from [A-Za-z0-9._~-], compute code_challenge = base64url(SHA256(verifier)).
  5. 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
  6. 6After redirect, exchange the code for tokens at POST https://api.etsy.com/v3/public/oauth/token with code_verifier included.
  7. 7Store the access_token (1 hour) and refresh_token (90 days) securely.
  8. 8For commercial use across multiple sellers' shops, submit a 'Request Commercial Access' application in the developer portal.
auth.py
1import os, secrets, hashlib, base64, requests
2
3KEYSTRING = os.environ['ETSY_KEYSTRING']
4SECRET = os.environ['ETSY_SECRET']
5REDIRECT_URI = os.environ['ETSY_REDIRECT_URI']
6
7# Step 1: Generate PKCE code verifier and challenge
8code_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('=')
12
13# Step 2: Build authorization URL
14auth_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}')
20
21# 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': verifier
29 })
30 resp.raise_for_status()
31 return resp.json() # {access_token, refresh_token, token_type, expires_in}
32
33def 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_token
38 })
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

GET/shops/{shop_id}/receipts

Fetch shop orders (receipts) with optional filters. Use `was_paid=true` to get only paid orders. Returns buyer info, line items, and totals.

ParameterTypeRequiredDescription
shop_idnumberrequiredYour Etsy shop ID
was_paidbooleanoptionalFilter to paid orders only
min_creatednumberoptionalUnix timestamp — only receipts created after this time
limitnumberoptionalResults per page (default 25, max 100)

Request

json
1GET https://api.etsy.com/v3/application/shops/12345678/receipts?was_paid=true&min_created=1714000000&limit=25

Response

json
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!"}]}
GET/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.

ParameterTypeRequiredDescription
shop_idnumberrequiredYour Etsy shop ID
receipt_idnumberrequiredThe receipt ID from the webhook payload

Request

json
1GET https://api.etsy.com/v3/application/shops/12345678/receipts/9876543

Response

json
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!"}
POSTWebhook subscription

Subscribe 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.

ParameterTypeRequiredDescription
eventstringrequiredorder.paid or order.canceled
urlstringrequiredHTTPS callback URL for webhook delivery

Request

json
1Configure via Etsy Developer Portal at https://www.etsy.com/developers/your-apps → select app → Webhooks tab

Response

json
1{"webhook_id": "evt_abc123", "shop_id": 12345678, "fetch_url": "https://api.etsy.com/v3/application/shops/12345678/receipts/9876543"}

Step-by-step automation

1

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.

request.sh
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.

2

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.

request.sh
1# Webhook verification: build signed_content = webhook-id + "." + webhook-timestamp + "." + raw_body
2# Decode whsec_ secret, compute HMAC-SHA256, compare base64 to webhook-signature header
3# No direct cURL example register webhook via the Developer Portal UI

Pro 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.

3

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.

request.sh
1# Fetch single receipt
2curl -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.

4

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.

request.sh
1# Poll for orders created in the last 5 minutes
2TIMESTAMP=$(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.

automate_etsy_messages.py
1import os, hmac, hashlib, base64, time, logging, requests
2from flask import Flask, request, abort
3
4logging.basicConfig(level=logging.INFO)
5app = Flask(__name__)
6
7KEYSTRING = 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']
12
13_token_cache = {'token': None, 'expires_at': 0, 'refresh': REFRESH_TOKEN}
14
15def 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']
24
25def 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()
30
31def 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 False
37 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())
41
42def format_price(p):
43 return p['amount'] / p['divisor']
44
45@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 '', 200
56 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 '', 200
62 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 '', 200
70
71if __name__ == '__main__':
72 app.run(port=3000)

Error handling

401Unauthorized — missing or invalid credentials
Cause

Either 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.

Fix

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.

Retry strategy

Refresh the access token using the refresh token, then retry the request once.

403Forbidden — scope not authorized
Cause

Missing transactions_r scope for receipts access, or buyer_email access not separately approved for buyer email addresses.

Fix

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.

Retry strategy

No retry — re-authorize with correct scopes.

429Rate limit exceeded
Cause

Hit the 10 req/sec or 10,000 req/day cap. Sliding window means daily quota doesn't fully reset at midnight.

Fix

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.

Retry strategy

Exponential backoff starting at 200ms. Check Etsy's rate-limit headers for remaining quota.

invalid_grantOAuth token exchange: invalid_grant
Cause

The authorization code was already used (each code is single-use), or the refresh token was revoked because the user removed the app.

Fix

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.

Retry strategy

No retry — initiate fresh OAuth flow.

404Not found — receipt does not exist
Cause

The 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.

Fix

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.

Retry strategy

No retry — verify the shop association and receipt ID.

Rate Limits for Etsy API

ScopeLimitWindow
Per app — requests per second10 req/secNew apps: 5 req/sec until commercially approved
Per app — requests per day10,000 req/daySliding 24-hour window (not midnight reset)
Access token lifetime3,600 seconds (1 hour)Per token; refresh with refresh_token
Refresh token lifetime90 daysRevoked on user app removal
retry-handler.ts
1import time
2
3def etsy_get_with_retry(path, params=None, max_retries=4):
4 delay = 0.2
5 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=params
10 )
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 continue
16 if resp.status_code == 401:
17 # Token expired refresh and retry once
18 _token_cache['expires_at'] = 0
19 continue
20 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

intermediate

Send a personalized thank-you email immediately after order payment, including items ordered and a custom message about processing time.

Shipping Notification

intermediate

Send a shipping confirmation email when you add tracking to the Etsy order, pairing tracking upload with email notification.

Review Request Sequence

advanced

7 days after estimated delivery, send an automated review request email with a direct link to the Etsy review page.

VIP Customer Recognition

advanced

Query 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/month

Zapier's Etsy integration triggers on new orders and can send Mailchimp or Gmail emails with order details.

Pros
  • + Zero code
  • + Native Etsy + Mailchimp integration
  • + Easy setup
Cons
  • - 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/month

Make supports Etsy as a trigger and can route order data to SendGrid or Gmail for post-purchase emails.

Pros
  • + More affordable than Zapier
  • + Better data transformation
  • + Conditional logic
Cons
  • - Polling-based, not real-time
  • - Limited Etsy scope support
  • - Complex for multi-step flows

n8n

Self-hosted free; Cloud Starter €20/month

n8n can poll Etsy's receipts endpoint via HTTP Request nodes and trigger email sends through SendGrid or Resend.

Pros
  • + Self-hosted free
  • + Full HTTP request control for custom headers
  • + Cron scheduling
Cons
  • - 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.

ChatGPT / Claude Prompt

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.

Lovable / V0 Prompt

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.

RapidDev

Need this automated?

Our team has built 600+ apps with API automations. We can build this for you.

Book a free consultation

Skip the coding — we'll build it for you

Our experts have built 600+ API automations. From prototype to production in days, not weeks.

Book a free consultation

We put the rapid in RapidDev

Need a dedicated strategic tech and growth partner? Discover what RapidDev can do for your business! Book a call with our team to schedule a free, no-obligation consultation. We'll discuss your project and provide a custom quote at no cost.