SelfServe is an order-editing, cancellation, and upselling app embedded in the Shopify admin. This page documents the Klaviyo OAuth integration: how it works, how to connect it, what events it sends, and how to verify it end-to-end.

How It Works

Customer Workflow

The merchant is the customer of SelfServe. The customer-facing flow for installing and using the Klaviyo integration:

1
The merchant installs SelfServe on their Shopify store via the Shopify App Store. SelfServe is an order-editing, cancellation, and upselling app embedded in the Shopify admin.
2
From the SelfServe admin, the merchant clicks Integrations in the side navigation, then Manage on the Klaviyo card.
3
On the Klaviyo integration detail page, the merchant clicks Connect Klaviyo.
4
The browser is redirected to Klaviyo’s OAuth consent screen. The merchant signs in to Klaviyo (if not already authenticated) and approves the requested scopes (events:write accounts:read).
5
Klaviyo redirects the merchant back to SelfServe’s callback URL. SelfServe exchanges the authorization code for access and refresh tokens (PKCE), persists them, and fetches the account name via GET /api/accounts/.
6
The merchant lands back on /app/integrations/klaviyo with the card showing Connected to {account name} along with an in-line reference to the four metric names SelfServe will emit.
7
The merchant goes to Klaviyo’s flow builder. The four metric names — Edits Allowed, One Hour Left to Edit, Editing Disallowed, Payment Due — appear in the trigger metric dropdown after the first event of each is received.
8
As customers interact with the merchant’s storefront (orders are placed, edited, and committed), SelfServe automatically pushes events to Klaviyo via POST /api/events/. These events trigger the merchant’s email/SMS flows.
9
To revoke access, the merchant clicks Disconnect in SelfServe. SelfServe calls POST /oauth/revoke to invalidate the grant on Klaviyo’s side and nullifies tokens locally. App uninstall performs the same revoke and cleanup automatically.
Integration Details

Endpoints and Scopes

API revision header: revision: 2026-04-15 on every authenticated API call.

Pushing lifecycle events from SelfServe to merchant’s Klaviyo account
Endpoint
POST https://a.klaviyo.com/api/events/
Scope
events:write
Trigger
Order created, edit-window deadline approaching, edit window closed, additional-payment window opened
Displaying the connected Klaviyo account name in SelfServe’s UI for merchant confirmation
Endpoint
GET https://a.klaviyo.com/api/accounts/
Scope
accounts:read
Trigger
One-time on OAuth callback success
Exchanging the OAuth authorization code for access and refresh tokens
Endpoint
POST https://a.klaviyo.com/oauth/token
Scope
OAuth, no API scope
Trigger
One-time on connect
Refreshing the access token when it expires (~1h cadence)
Endpoint
POST https://a.klaviyo.com/oauth/token
Scope
OAuth, no API scope
Trigger
Lazy — checked before each event push
Revoking the OAuth grant on explicit disconnect or app uninstall
Endpoint
POST https://a.klaviyo.com/oauth/revoke
Scope
OAuth, no API scope
Trigger
Disconnect button click; uninstall webhook
Scope Justification

Why We Request These Permissions

SelfServe requests exactly two scopes, both essential to the integration. No other Klaviyo scopes are requested. SelfServe does not read profiles, segments, lists, campaigns, flows, metrics, or any other Klaviyo data.

events:write
Required to push the four custom metric events that drive the merchant’s marketing flows. This is the primary purpose of the integration.
accounts:read
Required only to read the connected Klaviyo account’s display name so SelfServe’s UI can show ‘Connected to {account name}’ after OAuth completes. Used at most once per connection. No customer profile data, segment data, or list data is read with this scope.
Event Lifecycle

Four Events Fired by SelfServe

events:write
Edits Allowed
An order is created with an edit window.
Shopify orders/create webhook handler
events:write
One Hour Left to Edit
A fixed-deadline edit window has ~1h remaining.
SelfServe background scanner (10s polling)
events:write
Editing Disallowed
The edit window closes for any reason.
SelfServe service callback on window closure
events:write
Payment Due
An order edit creates an additional-payment window.
SelfServe service after Shopify orderEditCommit
Demo

Recorded Demo

A recorded demo of installation and the four use cases is available at the link below. The demo covers:

1
SelfServe installation on a test Shopify store.
2
Connecting Klaviyo via OAuth from the Integrations page.
3
Triggering each of the four events end-to-end and confirming they appear as activities on a Klaviyo profile.
4
Disconnecting Klaviyo and confirming the OAuth grant is revoked on Klaviyo’s side.
<<FILL IN: video URL — Loom, YouTube unlisted, or Google Drive>>
Test Access

Test Access Provisioning

The following steps validate every code path the integration exercises. Two pieces of test access are required before starting.

Shopify test store
Store URL
<<FILL IN: e.g., https://selfserve-klaviyo-test.myshopify.com>>
Storefront password (if password-protected)
<<FILL IN>>
Admin access
SelfServe will be installed and ready. Klaviyo’s review team should email <<FILL IN: your team’s contact email>> with the Shopify email address they’d like added as a staff account; SelfServe’s team will add them within one business day.
Test Klaviyo account access
Account share
Per Klaviyo’s policy, test access is granted via account share rather than credential sharing. Klaviyo’s review team should provide the email address they want added to a dedicated test Klaviyo account, which SelfServe’s team will provision on request.
Contact
<<FILL IN: your team’s contact email>>
Test Steps

Test Steps 1–4

Step 1 — Install SelfServe and connect Klaviyo (events:write, accounts:read scopes; /oauth/token endpoint)
1
From the test Shopify store admin, install SelfServe (it should already be installed for the test store).
2
Navigate to Apps → SelfServe → Integrations → Klaviyo.
3
Click Connect Klaviyo. The OAuth consent screen appears showing two scopes: “Events: Write” and “Accounts: View content and information.”
4
Approve. The browser redirects back to SelfServe’s integration detail page.
5
Verify: the page now shows Connected to {account name}, where {account name} is the organization name of the connected Klaviyo account. This confirms GET /api/accounts/ succeeded.
Step 2 — Trigger Edits Allowed (events:write scope; POST /api/events/)
1
From the test Shopify storefront (or via the Shopify admin’s “Create order” flow), place a new order.
2
Within seconds, the SelfServe order-windows system creates an EDIT_WINDOW for the order. SelfServe’s webhook handler then fires Edits Allowed to Klaviyo.
3
In the connected Klaviyo account, navigate to Analytics → Metrics. After a few seconds, Edits Allowed should appear in the metric list. Click into it; the most recent event should match the order placed in step 2.
4
Verify event properties include OrderId, OrderName, Shop, and at minimum EditsClosureStrategy (always present). For fixed-window strategies, EditsAllowedUntil and EditsAllowedUntilDisplay are also present. Per-capability variants (ItemEditingAllowedUntil, AddressEditingAllowedUntil) are also included.
Step 3 — Trigger Payment Due (events:write scope; POST /api/events/)
1
From the customer’s order in Shopify (use the test store’s customer account or a self-serve link), edit the order to add a more expensive item. This creates an outstanding amount.
2
SelfServe creates an additional-payment window and fires Payment Due.
3
In the connected Klaviyo account, verify Payment Due appears as a metric and the event payload includes PaymentUrl, AmountDue, Currency, PaymentDueAt, and OrderId.
4
Verify clicking the PaymentUrl opens Shopify’s hosted payment page for the outstanding amount.
Step 4 — Trigger One Hour Left to Edit (events:write scope; POST /api/events/)
1
In SelfServe’s order-window settings, configure a fixed-deadline edit window of, e.g., 65 minutes (must be > 1 hour to qualify for the warning).
2
Place a new order. Wait ~5 minutes (until the deadline is < 60 minutes away).
3
SelfServe’s One-Hour-Left scanner runs every 10 seconds. Within seconds of crossing the threshold, One Hour Left to Edit fires once for that order.
4
In Klaviyo, verify the event with the same property shape as Edits Allowed.
Test Flow (Steps 5–8)

Advanced Test Cases

Step 5 — Trigger Editing Disallowed (events:write scope; POST /api/events/)

1
Either let the edit window from step 4 expire naturally, OR cancel the order from the merchant admin.
2
SelfServe fires Editing Disallowed with ClosureReason indicating the cause (TIME_EXPIRED, ORDER_CANCELLED, ORDER_FULFILLED, etc.).
3
In Klaviyo, verify the event payload’s ClosureReason property matches the actual closure cause.

Step 6 — Verify token refresh (/oauth/token endpoint)

1
SelfServe’s access tokens expire 1 hour after issuance. Klaviyo issues a new refresh token on every refresh; SelfServe persists the rotated value.
2
To exercise this without waiting, a SelfServe team member can manually expire the test merchant’s access_token_expires_at in the test environment’s database. The next event push will trigger a refresh transparently. The merchant experience is identical (no re-auth required).
3
No direct merchant-facing surface for this step; it’s verifiable from SelfServe’s logs.

Step 7 — Verify disconnect (/oauth/revoke endpoint)

1
From SelfServe’s Integrations → Klaviyo page, click Disconnect.
2
SelfServe calls POST /oauth/revoke with the merchant’s refresh token, then nullifies the merchant’s tokens locally. The page returns to the disconnected state.
3
In the connected Klaviyo account’s Account → Settings → API Keys / OAuth Apps view, verify the SelfServe grant no longer appears.
4
Subsequent test events from SelfServe to Klaviyo are silently skipped (no errors visible to merchants or customers).

Step 8 — Verify uninstall cleanup

1
From the Shopify admin, uninstall SelfServe.
2
Shopify fires the app/uninstalled webhook to SelfServe.
3
SelfServe handles the webhook by calling POST /oauth/revoke (best-effort — does not block uninstall) and nullifying local tokens.
4
Verify Klaviyo’s grant page shows the SelfServe grant removed.
API Responses

Expected Response Codes per Endpoint

202 Accepted

POST /api/events/

202 Accepted on success. Primary event-push endpoint used for all four lifecycle events.
200 OK

GET /api/accounts/

200 OK on success. SelfServe falls back gracefully if this returns an error — the integration still connects, just without an account name.
200 OK

POST /oauth/token

200 OK on success. Used for both initial token exchange and all subsequent refresh operations.
200 OK

POST /oauth/revoke

200 OK on success. SelfServe treats failures as best-effort and does not block disconnect or uninstall.
App Review

Klaviyo App Review Checklist

Technical Review

Install URL directs the merchant to SelfServe’s Shopify App Store listing, which initiates Shopify’s OAuth + SelfServe’s installation flow. After installation completes, the merchant is redirected to SelfServe’s onboarding screen.
OAuth flow uses PKCE (code_challenge + code_challenge_method=S256) and a server-side state token persisted with one-hour expiry; state is validated on callback.
Access token expiry is honored; SelfServe lazily refreshes before any event push when the token is within 60 seconds of expiration.
Refresh token rotation: SelfServe persists the new refresh_token returned by every refresh response.
On invalid_grant from refresh, SelfServe nullifies all token columns and surfaces the integration as disconnected on next merchant interaction.
Mid-flight 401 from event push triggers exactly one forced refresh + retry. A second 401 nullifies tokens and treats the integration as disconnected.
POST /oauth/revoke is called on both explicit disconnect and Shopify app uninstall.
Every event POST carries a deterministic unique_id scoped to the relevant SelfServe window (<metric>-<windowId>) so retried webhooks and process restarts cannot produce duplicate Klaviyo events.
Scopes requested at authorize time exactly match the scopes used at runtime: events:write and accounts:read. No additional scopes are requested or used.

General Documentation & Form Submissions

App listing description accurately describes SelfServe and the Klaviyo integration’s customer value: it lets merchants drive Klaviyo email/SMS flows from order-edit lifecycle moments customers care about (edit deadlines, payment due, closure).
App listing identifies 2–6 key features, each with title ≤65 characters and description ≤225 characters. Titles drafted: "Edit-window deadlines", "Payment-due alerts", "Closure notifications", "OAuth in one click", "SMS-ready" (final list to confirm).
App listing links to 1–4 user guides / case studies hosted on SelfServe’s marketing site or Klaviyo’s partner blog.
Testing instructions document is hosted as a Google Doc with Commenter access granted to app.marketplace@klaviyo.com (this document — confirm sharing settings before submission).
At least 5 active user installs documented at submission time.

To Do Checklist (Operational)

Confirm test Shopify store admin access has been granted (or is one email request away).
Confirm test Klaviyo account access has been granted (or is one email request away).
Demo video URL embedded in this document is accessible to the review team without authentication.
All endpoints listed in Integration Details match the live runtime behavior; no discrepancies between requested-scope rationale and actual API calls.
OAuth app’s registered scope list on Klaviyo’s manage-apps page matches events:write accounts:read (no fewer, no more — Klaviyo silently drops unregistered scopes from authorize requests).
SelfServe team contact email is monitored for review-team correspondence: <<FILL IN: contact email>>
Provision test Shopify store and install SelfServe. Configure SelfServe with a fixed-deadline edit window for test orders. Create test Klaviyo account.
Add app.marketplace@klaviyo.com as Commenter on this doc. Record demo video covering all 4 events. Publish app listing copy in Klaviyo’s partner portal.
Confirm 5+ active installs and submit for review via Klaviyo’s partner submission form.
Contact

Questions During Review?

We respond within one business day.
Engineering / Technical
<<FILL IN: engineering contact email>>
Partnerships / Business
<<FILL IN: partnerships contact email>>

Stop answering "Can I change my order?" emails

Start your free trial today. Set up in under 10 minutes.