Selling merchandise inside your Network is a different job. To embed a Shopify storefront in a page, see How Do I Embed a Shopify Buy Button in My Mighty Network?, which needs no code.
How it works
- One ID per product. The plan decides which Spaces a customer gets. You can change that list in Mighty without redeploying your integration.
- Clean revocation. Removing the plan removes the Spaces that only it grants.
- Limited Members work. A customer who should get only the purchased Spaces, not your whole Network, can be created as a Limited Member holding that plan.
Before you begin
You need:- A Mighty Network on the Scale plan or above, which is required to create OAuth applications
- Host access to that Network
- A Shopify store where you can add webhooks
- A server you control that can receive HTTPS requests and store secrets, such as a small Node.js or Python app, or a cloud function
Step 1: Create a free plan for each product
In your Mighty Network, create one free Payment Plan for each Shopify product that grants access. Add the Spaces that product unlocks. The plan must be an active, free, subscription-style plan.createPaymentPlanMembership rejects paid plans and one-time purchase plans, because Shopify, not Mighty, has already taken payment. If you’ll grant the plan by creating a Limited Member (Step 4), it must also grant at least one Space and must not include the Network — createMember rejects a planId that includes the Network.
Keep every plan to Spaces only, including plans for FULL_MEMBER products. createMember already adds a full member to your Network, so the plan doesn’t need to. It also keeps refunds predictable: revoking a plan that includes the Network, in a Network people can’t join freely, removes the member from the whole Network unless something else still pays for their membership. See Revoke access on refund or cancellation.
Optionally, create a tag such as Shopify customer so you can find and segment these members later.
Step 2: Register an OAuth application
Your integration authenticates with OAuth 2.0 and acts as the Host who authorizes it.- In your Mighty Network, go to Network Admin > Integrations > OAuth Applications and click New OAuth Application.
- Choose the Confidential client type. Your integration runs on a server, so it can keep a Client Secret.
-
Register a redirect URI on your server, such as
https://integrations.example.com/mighty/callback. -
Select these scopes:
- Save the application and store the Client ID and Client Secret in your secret manager.
Step 3: Authorize the integration once
A Host completes the Authorization Code flow one time. Your service stores the resulting refresh token and uses it for every webhook after that. No customer ever signs in to your integration.1
Start authorization from your server
Add a route, such as
/mighty/connect, that generates a state nonce and a PKCE code_verifier, stores both in the server-side session, and redirects to https://YOUR_SUBDOMAIN.mn.co/oauth/authorize with the two scopes above.2
Sign in as a Host
Open
/mighty/connect and sign in with a Host account. Host scopes only work for Hosts, so a member or Moderator can’t complete this step.3
Exchange the code and store the tokens
On the callback, verify
state and iss, then exchange the code at /oauth/token. Store the refresh token encrypted at rest. It is the credential your integration runs on.state and the verifier, receives the callback, and exchanges the code itself.
Keep an access token fresh
Access tokens expire after one hour. Refresh on demand before you call the Mighty API, and persist the new refresh token whenever one is returned:mighty-auth.js
invalid_grant. Until a Host re-authorizes, the integration can’t grant access to new customers.
If multiple workers run this integration, serialize refreshes across processes too — for example, with a lock in the database that holds the refresh token — so only one worker ever exchanges a given refresh token.
Step 4: Map Shopify products to Mighty plans
Your integration needs to know which Mighty plan each Shopify product grants. Find the plan and tag IDs with the hosted GraphiQL explorer or a one-off query:config.js
LIMITED_MEMBER when a purchase should unlock only that plan’s Spaces, and FULL_MEMBER when the customer should also join your Network. A plan mapped to LIMITED_MEMBER must grant at least one Space and must not include the Network — confirm that when you create the plan in Step 1. See What is a Full vs. Limited Membership? for the difference.
Step 5: Receive Shopify webhooks
In Shopify, subscribe your service to these topics:
You can add webhooks in the Shopify admin under Settings > Notifications > Webhooks, or subscribe from a Shopify app. See Shopify’s webhooks documentation for both options.
Every delivery carries an
X-Shopify-Hmac-Sha256 header: a base64-encoded HMAC-SHA256 of the raw request body, signed with your webhook secret. Verify it against the raw body before you parse it, and reject anything that doesn’t match:
server.js
X-Shopify-Webhook-Id of each processed delivery and skip repeats — that handles duplicates. It doesn’t handle reordering: a retried orders/paid that arrives after refunds/create would re-grant access you already revoked. The handlers below record each refunded or canceled line item and skip it on a later orders/paid, which is what handles reordering. The Mighty mutations are idempotent too, so a retried grant or revoke reports the state you asked for instead of failing.
Step 6: Call the Mighty API
Wrap the GraphQL endpoint in a small helper. Remember that requests toapi.mn.co must send a User-Agent, and that GraphQL reports most errors with HTTP 200. Check the errors array on every response:
mighty.js
codes array carries each error’s extensions.code — for example NOT_FOUND or THROTTLED — so callers can branch on the reason instead of matching message text.
Find or create the member
Resolve each Shopify customer to a Mighty member in this order:- Your own records. Once you’ve resolved a customer, save their Mighty member ID against the Shopify customer ID (or, when an order carries no customer object, the normalized email) and reuse it.
memberByEmail. Finds an existing member by the order’s email address. A failed lookup (no member found) counts against a per-Network budget of 10 failed lookups every 10 minutes, and every first-time customer is one, so a busy store can hit it. Back off and retry whenmemberByEmailraisesTHROTTLED.createMember. Creates the account and adds it to your Network. Accounts belong to a single Network: if the address already has an account in your Network, such as a former member’s, it’s reused. An account in another Mighty Network isn’t.
handlers.js
createMember emails the new member a welcome. For a full member, the welcome carries a sign-in link. A Limited Member receives the plan’s welcome email instead. Pass sendWelcomeEmail: false if you’d rather send your own onboarding email from Shopify.
Grant the plan and tag the member
handlers.js
createPaymentPlanMembership doesn’t email the member. It reports ALREADY_SUBSCRIBED when the member already holds the plan, so repeated deliveries are safe.
The handler settles the member type once per order. If any mapped product in the order is FULL_MEMBER, a new customer is created as a full member and every plan is granted with createPaymentPlanMembership, so a mixed order gives the same result whatever order its line items arrive in.
The Mighty API has no mutation that promotes a Limited Member to a full member. Granting a free plan that includes the Network would, but this guide keeps plans to Spaces only, as recommended in Step 1, because revoking such a plan would remove the member from the Network. When a Limited Member buys a
FULL_MEMBER product, the handler still grants the plan, then routes the order to review so a Host can promote them in Network Admin. If you’d rather avoid manual steps, map every product to the same memberType.createTagMemberships reports SKIPPED_NO_MEMBERSHIP only for someone with no membership at all, such as a customer whose access you’ve already revoked.
Revoke access on refund or cancellation
handlers.js
deletePaymentPlanMembership is idempotent too: it reports NOT_SUBSCRIBED when the member no longer holds the plan.
Revoking a Spaces-only plan removes the member from the plan’s Spaces, with three exceptions. They keep:
- A Space that another plan or purchase still grants them.
- A Space a Host added them to directly.
- For a full member, any Space in the plan that’s open for Network members to join.
To remove a
FULL_MEMBER customer from the Network on a refund, call deleteNetworkMembership (scope host:write:network_members) after revoking the plan. The member keeps their content and can rejoin the Network later.handleRefund adds up each product’s refunded quantity across every refund on the order, and revokes the plan only once all the units of that product are refunded. If a customer bought two units and you refund one, they keep access.
The same applies across orders. Before you revoke, check whether the customer holds another paid order for the same product that hasn’t been refunded. If they bought it twice, one refund shouldn’t remove access they’re still paying for.
Step 7: Test the integration
- Create a test product in Shopify, map it to a free plan, and place a test order with an email address that isn’t in your Network.
- Confirm the new member appears in your Network with the plan’s Spaces, and with the tag if you set one.
- Place a second order for the same product. The grant should report
ALREADY_SUBSCRIBED. - Refund the order and confirm the member loses the plan’s Spaces, apart from any the exceptions in Revoke access on refund or cancellation keep, and that a full member stays in your Network.
- Refund a
LIMITED_MEMBER-only order, leaving that customer with no current membership, then place an order for aFULL_MEMBERproduct with the same customer. Confirm they rejoin as a full member instead of the order stalling. - Place an order for two units of the product and refund one. Confirm the member keeps access, then refund the second unit and confirm access is revoked.
- Replay a webhook delivery from your logs and confirm your service skips it.
Go further
Once the core flow works, you can extend it in either direction:- Recurring access. If you sell subscriptions through a Shopify subscription app, grant on each successful renewal and revoke when the subscription is canceled or payment fails.
- Mighty to Shopify. Register a Mighty webhook with
createWebhookCallback(requires thehost:write:network_integrationsscope) to react to events such asMEMBER_JOINEDorMEMBER_COURSE_PROGRESS_COMPLETED. You could tag a Shopify customer when they finish a course, or issue a discount code to members of a Space. - Reconciliation. Run a nightly job that lists members of each plan with
network { members(planId: ...) }and compares them to your paid Shopify orders. It catches anything a missed webhook left out of sync.
Troubleshooting
The token refresh returns invalid_grant
The refresh token is no longer valid. For example, it was revoked, or it was already exchanged by a concurrent refresh. See Refreshing tokens for what invalidates one. Have a Host open /mighty/connect again.
Mighty API calls return HTTP 401 after a successful refresh
Most often, the Host who authorized the integration is no longer a Host. The token refresh doesn’t check the role, but every API call does, so have a current Host open /mighty/connect again. You also get a 401 if that account was banned, or if your Network’s plan no longer includes the Mighty API.
createPaymentPlanMembership returns “Only free plans are supported.”
The mapped plan charges a price in Mighty. Point the product at a free plan, since Shopify has already collected payment.
createMember returns “This person is already a member of this Network.”
The customer is already a member, but memberByEmail couldn’t return them. See the warning in Find or create the member. Look up the member in Network Admin, save their member ID against the Shopify customer, and replay the order.
Requests fail with a JSON parse error and HTTP 403
Your requests to api.mn.co are missing a User-Agent header, so bot protection returned an HTML page. Set one on every request.
Related
Mighty API
Endpoint, request format, rate limits, and errors.
Authentication
OAuth flows, token refresh, and revocation.
OAuth Client Architectures
Where tokens should live for a server-side integration.
GraphQL Schema Explorer
Browse every query and mutation used in this guide.