Skip to main content
RapidDev - Software Development Agency
API AutomationsShopifyBearer Token

How to Automate Shopify Abandoned Cart Recovery using the API

Build a custom abandoned cart recovery flow using Shopify's `checkouts/create` webhook and the GraphQL `abandonedCheckouts` query. The catch: the webhook fires on cart creation, not abandonment — you need a delay-and-check pattern (wait 1-3 hours, then query to confirm the checkout is still incomplete). Send recovery sequences via an external ESP, not Shopify's API. GraphQL cost: 1,000-point bucket, 50 pts/sec restore on Standard plans.

Need help automating? Talk to an expert
4.9Clutch rating
600+Happy partners
17+Countries served
190+Team members
Intermediate7 min read30-60 minutesShopifyMay 2026RapidDev Engineering Team
TL;DR

Build a custom abandoned cart recovery flow using Shopify's `checkouts/create` webhook and the GraphQL `abandonedCheckouts` query. The catch: the webhook fires on cart creation, not abandonment — you need a delay-and-check pattern (wait 1-3 hours, then query to confirm the checkout is still incomplete). Send recovery sequences via an external ESP, not Shopify's API. GraphQL cost: 1,000-point bucket, 50 pts/sec restore on Standard plans.

API Quick Reference

Auth

OAuth 2.0 / Access Token

Rate limit

1,000-point bucket, 50 pts/sec restore

Format

JSON

SDK

Available

Understanding Shopify Abandoned Cart Recovery via API

Shopify defines a checkout as 'abandoned' after 1 hour of inactivity. However, the `checkouts/create` webhook fires immediately when a checkout is created — not when it's abandoned. This timing mismatch is the core architectural challenge. Your automation must detect cart creation, schedule a delayed check, verify the checkout is still incomplete after the abandonment window, and only then trigger recovery messaging.

The `abandonedCheckouts` GraphQL query returns checkouts that have not been completed. It includes the customer's email (if provided), line items, totals, and a `recoveryUrl` — Shopify's pre-built recovery link that restores the cart for the customer.

For multi-step sequences (email at 1h, email+discount at 24h, SMS at 48h), you need a job queue (Redis+BullMQ, Celery, or a managed service like Inngest). The draft order approach (`draftOrderCreate`) lets you create a discount-linked recovery experience by creating a draft order with a discount applied. See https://shopify.dev/docs/api/admin-graphql/2026-04/queries/abandonedCheckouts for the full query reference.

Base URLhttps://{shop}.myshopify.com/admin/api/2026-04/graphql.json

Setting Up Shopify API Authentication for Cart Recovery

Cart recovery requires reading checkout data and optionally creating draft orders with discounts. Set up a custom app via the Shopify Dev Dashboard with the required scopes. You'll also need an ESP API key for sending recovery emails.

  1. 1Go to dev.shopify.com and create a custom app.
  2. 2Enable Admin API scopes: read_checkouts, read_customers, write_draft_orders.
  3. 3Install the app and copy the generated access token.
  4. 4Store as SHOPIFY_ACCESS_TOKEN environment variable.
  5. 5Set SHOPIFY_APP_SECRET for webhook HMAC validation.
  6. 6Set up your job queue (Redis + BullMQ, Celery, or similar) for delayed processing.
  7. 7Configure your ESP credentials (SENDGRID_API_KEY or equivalent).
auth.py
1import os
2import requests
3
4SHOP_DOMAIN = os.environ['SHOPIFY_SHOP_DOMAIN']
5ACCESS_TOKEN = os.environ['SHOPIFY_ACCESS_TOKEN']
6GRAPHQL_URL = f'https://{SHOP_DOMAIN}/admin/api/2026-04/graphql.json'
7HEADERS = {'X-Shopify-Access-Token': ACCESS_TOKEN, 'Content-Type': 'application/json'}
8
9# Test: query for recently abandoned checkouts
10query = '{ abandonedCheckouts(first: 3) { edges { node { id createdAt totalPriceV2 { amount } } } } }'
11resp = requests.post(GRAPHQL_URL, json={'query': query}, headers=HEADERS)
12print(resp.json())

Security notes

  • Store all credentials (Shopify token, app secret, ESP key) in environment variables.
  • Validate checkout webhook HMAC using base64(HMAC-SHA256(rawBody, appSecret)).
  • Cart recovery emails use customer PII — never log email addresses or cart contents in plain text.
  • The checkout `recoveryUrl` contains a secure token — treat it as sensitive and don't log it.
  • Rate-limit your webhook endpoint to prevent replay attacks.
  • Use HTTPS for all webhook callback URLs.

Key endpoints

POST/admin/api/2026-04/graphql.json

GraphQL query for `abandonedCheckouts` — returns checkouts that haven't been completed, with line items, customer info, and pre-built recovery URL.

ParameterTypeRequiredDescription
firstnumberrequiredNumber of results per page (use 10 to limit query cost)
afterstringoptionalPagination cursor for subsequent pages

Request

json
1{"query": "{ abandonedCheckouts(first: 10) { edges { node { id abandonedCheckoutUrl createdAt updatedAt email totalPriceV2 { amount currencyCode } lineItems(first: 10) { edges { node { title quantity } } } } } pageInfo { hasNextPage endCursor } } }"}

Response

json
1{"data": {"abandonedCheckouts": {"edges": [{"node": {"id": "gid://shopify/AbandonedCheckout/abc123", "abandonedCheckoutUrl": "https://mystore.myshopify.com/12345/checkouts/abc123/recover", "createdAt": "2026-04-15T09:00:00Z", "updatedAt": "2026-04-15T09:05:00Z", "email": "customer@example.com", "totalPriceV2": {"amount": "89.99", "currencyCode": "USD"}, "lineItems": {"edges": [{"node": {"title": "Blue Widget", "quantity": 2}}]}}}]}}}
POST/admin/api/2026-04/graphql.json

GraphQL mutation `webhookSubscriptionCreate` for `checkouts/create` — fires immediately when a checkout is created (not when it's abandoned).

ParameterTypeRequiredDescription
topicstringrequiredCHECKOUTS_CREATE — fires on every new checkout

Request

json
1{"query": "mutation { webhookSubscriptionCreate(topic: CHECKOUTS_CREATE, webhookSubscription: {callbackUrl: \"https://yourapp.com/webhooks/checkout-create\", format: JSON}) { userErrors { field message } webhookSubscription { id } } }"}

Response

json
1{"data": {"webhookSubscriptionCreate": {"userErrors": [], "webhookSubscription": {"id": "gid://shopify/WebhookSubscription/334455"}}}}
POST/admin/api/2026-04/graphql.json

GraphQL mutation `draftOrderCreate` — creates a draft order with a discount code for personalized recovery offers.

ParameterTypeRequiredDescription
lineItemsarrayrequiredArray of {variantId, quantity} from the abandoned cart
appliedDiscountobjectoptionalDiscount to apply: {value, valueType: PERCENTAGE|FIXED_AMOUNT, title}

Request

json
1{"query": "mutation draftOrderCreate($input: DraftOrderInput!) { draftOrderCreate(input: $input) { draftOrder { id invoiceUrl } userErrors { field message } } }", "variables": {"input": {"lineItems": [{"variantId": "gid://shopify/ProductVariant/12345", "quantity": 2}], "appliedDiscount": {"value": 10, "valueType": "PERCENTAGE", "title": "RECOVER10"}, "email": "customer@example.com"}}}

Response

json
1{"data": {"draftOrderCreate": {"draftOrder": {"id": "gid://shopify/DraftOrder/567890", "invoiceUrl": "https://mystore.myshopify.com/drafts/abc"}, "userErrors": []}}}

Step-by-step automation

1

Receive Cart Creation Webhook and Schedule Delayed Check

Why: Shopify fires the checkout webhook on creation, not on abandonment — you must delay the recovery check by 1-3 hours to avoid emailing customers who are still actively shopping.

Register the `CHECKOUTS_CREATE` webhook. When it fires, extract the checkout token and email. If an email is available, schedule a delayed job to run after 1 hour to verify the checkout is still incomplete. Return 2xx immediately to avoid delivery failures.

request.sh
1# Register checkouts/create webhook
2curl -X POST 'https://mystore.myshopify.com/admin/api/2026-04/graphql.json' \
3 -H 'X-Shopify-Access-Token: shpat_your_token' \
4 -H 'Content-Type: application/json' \
5 -d '{"query": "mutation { webhookSubscriptionCreate(topic: CHECKOUTS_CREATE, webhookSubscription: {callbackUrl: \\"https://yourapp.com/webhooks/checkout-create\\", format: JSON}) { userErrors { field message } webhookSubscription { id } } }"}'

Pro tip: Only schedule recovery for checkouts that have an email address — anonymous checkouts (no email) cannot be recovered via email. This typically filters out 30-50% of checkouts.

Expected result: A delayed job is queued for 1 hour after checkout creation. Your webhook endpoint responds immediately with 200.

2

Check if Checkout is Still Abandoned

Why: Many customers complete their purchase between the cart creation webhook and your 1-hour check — you must verify abandonment before sending recovery emails to avoid annoying paying customers.

After the delay, query `abandonedCheckouts` to check if the checkout is still incomplete. The checkout will not appear in `abandonedCheckouts` if the order was completed. If it's still there, fetch the full cart details including line items and recovery URL for the email.

request.sh
1# Query abandoned checkouts (filter by date to find the specific one)
2curl -X POST 'https://mystore.myshopify.com/admin/api/2026-04/graphql.json' \
3 -H 'X-Shopify-Access-Token: shpat_your_token' \
4 -H 'Content-Type: application/json' \
5 -d '{"query": "{ abandonedCheckouts(first: 10) { edges { node { id abandonedCheckoutUrl email totalPriceV2 { amount currencyCode } lineItems(first: 10) { edges { node { title quantity originalUnitPriceV2 { amount } } } } } } } }"}'

Pro tip: Add a second check at 24 hours with a discount incentive — multi-step recovery sequences (1h email, 24h email with 10% off) typically recover 2-3x more carts than single-email flows.

Expected result: Returns the abandoned checkout object with line items and recovery URL, or null if the customer completed the purchase.

3

Send Recovery Email with Cart Contents and Recovery Link

Why: The personalized recovery email with line items and a direct cart recovery link is what actually converts abandoned carts back to purchases.

Build the recovery email with the cart's line items, total, and `abandonedCheckoutUrl` (Shopify's pre-built recovery link that restores the session). Send via your ESP. For the second recovery email, consider creating a draft order with a discount using `draftOrderCreate` and linking to that instead.

request.sh
1# Send recovery email via SendGrid
2curl -X POST 'https://api.sendgrid.com/v3/mail/send' \
3 -H 'Authorization: Bearer $SENDGRID_API_KEY' \
4 -H 'Content-Type: application/json' \
5 -d '{
6 "from": {"email": "hello@yourstore.com"},
7 "personalizations": [{
8 "to": [{"email": "customer@example.com"}],
9 "dynamic_template_data": {
10 "cart_total": "89.99",
11 "currency": "USD",
12 "recovery_url": "https://mystore.myshopify.com/...",
13 "items": [{"title": "Blue Widget", "quantity": 2}]
14 }
15 }],
16 "template_id": "d-your-abandoned-cart-template"
17 }'

Pro tip: Use Shopify's `abandonedCheckoutUrl` (not a custom URL) for the recovery link — it restores the customer's exact cart session including any gift card or discount code they had applied.

Expected result: Customer receives a personalized email showing their cart items, total, and a direct link to complete their purchase.

4

Create Discount Draft Order for Second Recovery Email

Why: A 10-15% discount in the 24-hour follow-up email significantly increases cart recovery rates — the `draftOrderCreate` mutation creates a pre-discounted checkout link.

If the customer still hasn't purchased after the first recovery email, create a draft order using the abandoned cart's line items with a percentage discount applied. The `invoiceUrl` from the draft order becomes the recovery link in your second email, giving the customer a pre-applied discount.

request.sh
1curl -X POST 'https://mystore.myshopify.com/admin/api/2026-04/graphql.json' \
2 -H 'X-Shopify-Access-Token: shpat_your_token' \
3 -H 'Content-Type: application/json' \
4 -d '{
5 "query": "mutation draftOrderCreate($input: DraftOrderInput!) { draftOrderCreate(input: $input) { draftOrder { id invoiceUrl } userErrors { field message } } }",
6 "variables": {
7 "input": {
8 "email": "customer@example.com",
9 "lineItems": [{"variantId": "gid://shopify/ProductVariant/12345", "quantity": 2}],
10 "appliedDiscount": {"value": 10, "valueType": "PERCENTAGE", "title": "RECOVER10"},
11 "note": "Abandoned cart recovery offer"
12 }
13 }
14 }'

Pro tip: Set draft order expiry if you don't want the discount available indefinitely. Draft orders can be deleted via `draftOrderDelete` if the customer doesn't use them within your campaign window.

Expected result: Returns an `invoiceUrl` — a Shopify-hosted checkout page with the discount pre-applied. Use this URL in your 24-hour recovery email.

Complete working code

This script implements the full abandoned cart pipeline: webhook receipt, 1-hour delayed abandonment check, first recovery email send, and 24-hour follow-up with discount offer.

automate_shopify_cart_recovery.py
1import os, hmac, hashlib, base64, logging, requests
2from flask import Flask, request, abort
3from celery import Celery
4
5logging.basicConfig(level=logging.INFO)
6app = Flask(__name__)
7celery = Celery('cart', broker=os.environ['REDIS_URL'])
8
9SHOP_DOMAIN = os.environ['SHOPIFY_SHOP_DOMAIN']
10ACCESS_TOKEN = os.environ['SHOPIFY_ACCESS_TOKEN']
11APP_SECRET = os.environ['SHOPIFY_APP_SECRET']
12SENDGRID_KEY = os.environ['SENDGRID_API_KEY']
13TEMPLATE_FIRST = os.environ['SENDGRID_CART_TEMPLATE_1']
14TEMPLATE_SECOND = os.environ['SENDGRID_CART_TEMPLATE_2']
15
16GQL_URL = f'https://{SHOP_DOMAIN}/admin/api/2026-04/graphql.json'
17GQL_HEADERS = {'X-Shopify-Access-Token': ACCESS_TOKEN, 'Content-Type': 'application/json'}
18
19def gql(query, variables=None):
20 resp = requests.post(GQL_URL, json={'query': query, 'variables': variables or {}}, headers=GQL_HEADERS)
21 resp.raise_for_status()
22 return resp.json()
23
24def find_abandoned_checkout(token):
25 q = '{ abandonedCheckouts(first: 20) { edges { node { id abandonedCheckoutUrl email totalPriceV2 { amount currencyCode } lineItems(first: 10) { edges { node { title quantity } } } } } } }'
26 checkouts = gql(q)['data']['abandonedCheckouts']['edges']
27 return next((e['node'] for e in checkouts if token in e['node'].get('abandonedCheckoutUrl', '')), None)
28
29def send_email(to_email, template_id, data):
30 payload = {'from': {'email': 'hello@yourstore.com'}, 'personalizations': [{'to': [{'email': to_email}], 'dynamic_template_data': data}], 'template_id': template_id}
31 resp = requests.post('https://api.sendgrid.com/v3/mail/send', json=payload, headers={'Authorization': f'Bearer {SENDGRID_KEY}'})
32 return resp.status_code == 202
33
34@celery.task
35def first_recovery_check(token, email):
36 checkout = find_abandoned_checkout(token)
37 if not checkout:
38 logging.info(f'Cart {token} completed, no recovery needed')
39 return
40 items = [{'title': e['node']['title'], 'qty': e['node']['quantity']} for e in checkout['lineItems']['edges']]
41 data = {'recovery_url': checkout['abandonedCheckoutUrl'], 'cart_total': checkout['totalPriceV2']['amount'], 'items': items}
42 if send_email(email, TEMPLATE_FIRST, data):
43 logging.info(f'First recovery email sent to {email}')
44 second_recovery_check.apply_async(args=[token, email], countdown=86400) # 24h
45
46@celery.task
47def second_recovery_check(token, email):
48 checkout = find_abandoned_checkout(token)
49 if not checkout:
50 return
51 data = {'recovery_url': checkout['abandonedCheckoutUrl'], 'cart_total': checkout['totalPriceV2']['amount'], 'discount': '10%'}
52 send_email(email, TEMPLATE_SECOND, data)
53 logging.info(f'Second recovery email (with discount) sent to {email}')
54
55@app.route('/webhooks/checkout-create', methods=['POST'])
56def checkout_create():
57 hmac_header = request.headers.get('X-Shopify-Hmac-Sha256', '')
58 computed = base64.b64encode(hmac.new(APP_SECRET.encode(), request.get_data(), hashlib.sha256).digest()).decode()
59 if not hmac.compare_digest(computed, hmac_header):
60 abort(401)
61 data = request.get_json()
62 if data.get('email') and data.get('token'):
63 first_recovery_check.apply_async(args=[data['token'], data['email']], countdown=3600)
64 return '', 200
65
66if __name__ == '__main__':
67 app.run(port=3000)

Error handling

Checkout not found in abandonedCheckoutsNo result — checkout was completed
Cause

The customer completed their purchase between the webhook firing and your 1-hour check. This is the expected success case — not an error.

Fix

Always treat a missing checkout as a success (order was placed) and cancel the recovery sequence. Never send recovery emails to customers who have already purchased.

Retry strategy

No retry — log as 'converted' and skip.

429Throttled
Cause

GraphQL point bucket exhausted, common when processing many abandoned carts simultaneously during a sale event.

Fix

Check `extensions.cost.throttleStatus.currentlyAvailable` and implement exponential backoff. For batch processing, stagger the 1-hour checks rather than having all carts check at the same time.

Retry strategy

Exponential backoff starting at 1 second. Re-queue the job with a 60-second delay.

200 with userErrorsdraftOrderCreate: variant not found or has been deleted
Cause

The product variant in the abandoned cart was deleted or sold out since the cart was created.

Fix

Skip variants that return errors in the draft order creation. Send the recovery email using the `abandonedCheckoutUrl` directly instead of the draft order URL when some items are unavailable.

Retry strategy

No retry — use the fallback abandonedCheckoutUrl.

403Missing required scope: read_checkouts
Cause

The app token was generated before the read_checkouts scope was added to the app configuration.

Fix

Add read_checkouts to the app scopes in the Dev Dashboard and reinstall the app to generate a new token with the updated scopes.

Retry strategy

No retry until re-authorized.

Rate Limits for Shopify Abandoned Cart API

ScopeLimitWindow
GraphQL Admin API — Standard plan1,000 pointsRestores at 50 points/second
abandonedCheckouts query with 10 lineItems~30 points per queryPer request
Webhook delivery timeout5 secondsPer delivery; failures accumulate toward 19-failure deletion
retry-handler.ts
1import time
2
3def gql_with_retry(url, headers, payload, max_retries=4):
4 delay = 1
5 for attempt in range(max_retries):
6 resp = requests.post(url, json=payload, headers=headers)
7 if resp.status_code == 429:
8 print(f'Rate limited, backing off {delay}s')
9 time.sleep(delay)
10 delay = min(delay * 2, 32)
11 continue
12 resp.raise_for_status()
13 result = resp.json()
14 available = result.get('extensions', {}).get('cost', {}).get('throttleStatus', {}).get('currentlyAvailable', 1000)
15 if available < 100:
16 time.sleep(2)
17 return result
18 raise RuntimeError('Rate limit retries exhausted')
  • Use `first: 10` on abandonedCheckouts lineItems to limit query cost — you rarely need more than 10 items for email display.
  • Stagger delayed jobs — if many carts were created at 3pm, don't have all 1-hour checks run at exactly 4pm simultaneously.
  • Cache the abandonment check result for 5 minutes — don't re-query the same checkout multiple times within a short window.
  • Use bulk querying when processing historical abandoned carts — paginate slowly with delays between pages.
  • Shopify's built-in abandoned cart email (Shopify notifications) counts against your 10,000 free monthly emails — custom API flows bypass this.

Security checklist

  • Validate every webhook HMAC before processing — use base64(HMAC-SHA256(rawBody)) not hex.
  • Never store full cart contents or email addresses in logs — only store checkout tokens and anonymized identifiers.
  • Implement job deduplication in your queue — prevent duplicate recovery emails if a webhook is re-delivered.
  • The abandonedCheckoutUrl contains a session token — treat it as sensitive data.
  • Check that the checkout email matches the customer who originally created it before sending recovery emails.
  • Implement an unsubscribe mechanism for cart recovery emails — not legally required but essential for deliverability.
  • Store all credentials (Shopify token, ESP key, Redis URL) in environment variables.
  • Use HTTPS for webhook endpoints and ESP API calls.

Automation use cases

Single Email Recovery

beginner

Simple 1-hour delay, one recovery email with cart contents and direct checkout link.

Multi-Step Recovery Sequence

intermediate

3-touch sequence: 1-hour email, 24-hour email with 10% discount, 48-hour SMS (via Twilio) for high-value carts.

Dynamic Discount Tiering

advanced

Offer larger discounts for higher-value abandoned carts — 5% for <$50, 10% for $50-$200, 15% for >$200.

Cross-Channel Recovery (Email + SMS)

advanced

Email recovery first, then SMS via Twilio for customers who opened the email but didn't click.

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 Shopify abandoned cart trigger can fire a Klaviyo or Mailchimp event for simple single-email recovery flows.

Pros
  • + Zero code setup
  • + Native Shopify + Klaviyo integration
  • + Easy delay configuration
Cons
  • - No multi-step delay logic without premium plans
  • - Can't create draft orders
  • - Limited cart data access

Make (formerly Integromat)

Free tier (1,000 ops/month); Core $9/month

Make supports Shopify checkout webhooks and can orchestrate multi-step recovery flows with conditional delays.

Pros
  • + Delay modules built-in
  • + More affordable than Zapier at scale
  • + Conditional logic support
Cons
  • - Complex to set up multi-step sequences
  • - No GraphQL access
  • - Debugging difficult

n8n

Self-hosted free; Cloud Starter €20/month

n8n's Wait node enables the delay-and-check pattern, and its HTTP Request node can call Shopify GraphQL for abandonment verification.

Pros
  • + Wait node handles delay natively
  • + GraphQL via HTTP node
  • + Self-hosted for free
Cons
  • - Complex workflow for multi-step sequences
  • - Requires infrastructure management
  • - Debugging multi-step flows is hard

Best practices

  • Never email customers who completed their purchase — always verify abandonment with a fresh API call at the time of sending.
  • Only recover checkouts that have an email address — anonymous checkouts account for 30-50% of all carts and cannot be recovered via email.
  • Use Shopify's `abandonedCheckoutUrl` as the recovery link — it restores the customer's exact cart session without requiring re-login.
  • Multi-step recovery sequences (1h email, 24h email with discount) recover 2-3x more carts than single-email campaigns.
  • Set reasonable discount expiry — a draft order discount should expire within 48-72 hours to create urgency.
  • Track recovery attribution — tag recovered orders in Shopify to measure campaign ROI.
  • Respect customers who don't respond to the first two recovery touches — don't spam with more than 2-3 recovery attempts.
  • Test the timing logic thoroughly — a bug that emails customers who just completed checkout is more damaging than not sending the email at all.

Ask AI to help

Copy one of these prompts to get a personalized, working implementation.

ChatGPT / Claude Prompt

I'm building a Shopify abandoned cart recovery system. My architecture: checkouts/create webhook → 1-hour delayed job (BullMQ) → query abandonedCheckouts to verify → SendGrid recovery email. My problem is: [describe issue]. Here's my delayed job code: [paste code]. The abandonedCheckouts query returns: [paste response]. Help me verify the timing logic and fix the checkout token matching.

Lovable / V0 Prompt

Build a Shopify abandoned cart recovery dashboard connected to Supabase. It should show a real-time feed of abandoned carts (fetched via Shopify GraphQL), the recovery sequence status per cart (pending 1h email, sent 1h email, sent 24h email, recovered), and aggregate recovery rate metrics. Include a configuration panel for discount percentages and delay timing. Use shadcn/ui and Tailwind CSS.

Frequently asked questions

When exactly does Shopify mark a checkout as abandoned?

Shopify marks a checkout as abandoned after 1 hour of inactivity — meaning 1 hour has passed since the last update to the checkout without a completed order. The `checkouts/create` webhook fires immediately when the checkout is created, not when it becomes abandoned. This is why you need a delay-and-check pattern: wait 1 hour after the webhook fires, then query `abandonedCheckouts` to see if the checkout is still incomplete.

Does the abandonedCheckouts query require any special scopes?

Yes, you need the `read_checkouts` scope on your custom app to query `abandonedCheckouts`. If your app token was generated before you added this scope, you'll need to reinstall the app from the Dev Dashboard to get a new token with the updated scope.

What if the customer doesn't have an email address in the cart?

If `email` is null on the checkout webhook payload, the cart cannot be recovered via email. Anonymous checkouts (no email, no account) typically represent 30-50% of all carts. For these, there's nothing you can do via API. Shopify's native exit-intent popups and cookie-based retargeting ads are the only options for anonymous abandonment.

What happens when I hit the GraphQL rate limit?

Shopify returns HTTP 429. For GraphQL, you can also see throttling in `extensions.cost.throttleStatus.currentlyAvailable`. On Standard plans, the 1,000-point bucket restores at 50 pts/sec. The `abandonedCheckouts` query with lineItems costs ~30 points. Implement exponential backoff and stagger job processing to avoid all abandoned cart checks hitting simultaneously.

Can I access the abandoned checkout data if the customer used guest checkout?

Yes — if the customer entered their email during guest checkout, you'll see it in the webhook payload and can query `abandonedCheckouts`. The distinction is login status: guest customers gave their email in the checkout form but didn't create an account. Their checkout is still recoverable as long as the email was captured before abandonment.

Is Shopify's built-in abandoned cart email good enough, or do I need the API?

Shopify's built-in single abandoned cart email works well for simple stores. Build a custom API-based flow when you need: multi-step sequences (2-3 touch points), dynamic discount offers based on cart value, SMS recovery, custom timing logic, or integration with your CRM/ESP for unified customer profiles.

Can RapidDev help build a custom abandoned cart recovery system?

Yes. RapidDev has built 600+ apps including full abandoned cart recovery pipelines with multi-step email sequences, dynamic discounting, and CRM integration. We handle the queue infrastructure, Shopify API integration, and ESP setup. 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.