Storefront Customer Portal API
Storefront API for customer self-service: pause, cancel, skip, swap products, update billing, and manage subscriptions.
Build custom membership management experiences in your Shopify theme. This API allows customers to manage their subscriptions directly from your storefront using Liquid variables for authentication.
Overview
The Storefront Portal API is designed for theme developers who want to create custom customer dashboards. Unlike the backend Membership Management API, this endpoint uses customer-specific tokens stored in Shopify metafields for authentication.
Endpoint
| Property | Value |
|---|---|
| URL | {store}.myshopify.com/apps/subscribfy-api/store/manage-membership-dashboard |
| Method | GET or POST |
| Content-Type | application/x-www-form-urlencoded |
Authentication
Authentication uses customer metafields set by Subscribfy. These are accessible in Liquid templates.
Required Parameters
| Parameter | Liquid Variable | Description |
|---|---|---|
cid | {{ customer.id }} | Shopify customer ID |
hash | {{ customer.metafields.exison.exison_hash }} | Subscribfy authentication token |
exm | 1 | Required flag (always set to 1) |
Base URL Template (Liquid)
{% assign base_url = shop.url | append: "/apps/subscribfy-api/store/manage-membership-dashboard" %}
{% assign auth_params = "?cid=" | append: customer.id | append: "&hash=" | append: customer.metafields.exison.exison_hash | append: "&exm=1" %}
{% assign api_url = base_url | append: auth_params %}Customer Metafields Reference
Subscribfy creates the following metafields on customer records:
Namespace: exison
| Key | Description |
|---|---|
exison_hash | Authentication token for API requests |
customer_subscription1 | Primary membership/subscription data (JSON) |
customer_subscription1_parent | Parent buyer info for gifted memberships |
customer_subscription1_gifts | Memberships this customer has gifted to others |
customer_subscription1 Structure
| Field | Type | Description |
|---|---|---|
name | string | Subscription/membership name |
status | string | ACTIVE, PAUSED, or CANCELLED |
member_since | string | Membership start date (e.g., "May 28, 2023") |
next_billing | string | Next billing date (e.g., "October 28, 2023") |
scid | string | Subscribfy contract ID (required for actions) |
Liquid Example: Display Membership Status
{% assign membership = customer.metafields.exison.customer_subscription1 %}
{% if membership %}
<h2>{{ membership.name }}</h2>
<p>Status: <strong>{{ membership.status }}</strong></p>
<p>Member since: {{ membership.member_since }}</p>
{% if membership.status == "ACTIVE" %}
<p>Next billing: {{ membership.next_billing }}</p>
{% endif %}
{% endif %}Available Actions
| Action | Description | Additional Parameters |
|---|---|---|
getStoreCreditHistory | Get customer's store credit transactions | None (no scid required) |
updatePaymentContent | Send payment method update email | scid |
updatePauseContent | Pause subscription | scid, period (1-3) |
updateCancelContent | Cancel subscription | scid, reason |
updateReactivateContent | Reactivate subscription | scid |
updateSkipContent | Skip next order (pause 1 month) | scid |
updateShipNowContent | Trigger immediate shipment | scid, info[update_billing_date] |
updateChargeNowContent | Trigger immediate charge | scid, info[update_billing_date] |
updateItemContent | Update item quantity | scid, info[line], info[qty] |
removeItemContent | Remove item from subscription | scid, info[line] |
swapProduct | Swap product in subscription | scid, info[line], info[product_id] |
ShowSwapContent | Get available products for swap | scid, info[productAction] |
updateRefundContent | Request refund | scid, order_gid (optional) |
Response Format
Response Codes
| Code | Meaning | Description |
|---|---|---|
result: 1 | Success | Action completed successfully |
result: -1 | Validation Error | Invalid customer, contract not found, or invalid payment method |
result: -2 | Unauthorized | Customer not allowed to perform this action |
result: 9 | Rate Limited | Too many requests (wait 15 seconds) |
Examples
Get Store Credit History
POST {store}/apps/subscribfy-api/store/manage-membership-dashboard
?cid={customer.id}
&hash={customer.metafields.exison.exison_hash}
&exm=1
&action=getStoreCreditHistoryResponse:
{
"result": 1,
"data": [
{
"body": "You've earned 29.00 Store Credits!",
"value": 29,
"currency": "USD",
"type": "Membership",
"status": "Active",
"for_order_gid": 5412270689000,
"created_at_unixtimestamp": 1735473052,
"created_at_human": "2024-12-29"
},
{
"body": "Discount Redemption",
"value": -10,
"currency": "USD",
"type": "redeem",
"status": "Active",
"for_order_gid": 655554481300,
"created_at_unixtimestamp": 1717223080,
"created_at_human": "2024-06-01"
}
]
}Pause Subscription
POST {store}/apps/subscribfy-api/store/manage-membership-dashboard
?cid={customer.id}
&hash={hash}
&exm=1
&scid={contract_id}
&action=updatePauseContent
&period=2Response:
{
"result": 1
}Cancel Subscription
POST {store}/apps/subscribfy-api/store/manage-membership-dashboard
?cid={customer.id}
&hash={hash}
&exm=1
&scid={contract_id}
&action=updateCancelContent
&reason=Too%20expensiveResponse:
{
"result": 1
}Update Payment Method
Sends an email to the customer with a link to update their payment method.
POST {store}/apps/subscribfy-api/store/manage-membership-dashboard
?cid={customer.id}
&hash={hash}
&exm=1
&scid={contract_id}
&action=updatePaymentContentResponse:
{
"result": 1
}Reactivate Subscription
POST {store}/apps/subscribfy-api/store/manage-membership-dashboard
?cid={customer.id}
&hash={hash}
&exm=1
&scid={contract_id}
&action=updateReactivateContentResponse:
{
"result": 1
}JavaScript Implementation
Basic API Call
const SUBSCRIBFY_API = {
baseUrl: '{{ shop.url }}/apps/subscribfy-api/store/manage-membership-dashboard',
cid: '{{ customer.id }}',
hash: '{{ customer.metafields.exison.exison_hash }}',
scid: '{{ customer.metafields.exison.customer_subscription1.scid }}',
async call(action, params = {}) {
const formData = new URLSearchParams({
cid: this.cid,
hash: this.hash,
exm: '1',
scid: this.scid,
action: action,
...params
});
const response = await fetch(this.baseUrl, {
method: 'POST',
body: formData
});
return response.json();
},
async pause(months) {
return this.call('updatePauseContent', { period: months });
},
async cancel(reason) {
return this.call('updateCancelContent', { reason: reason });
},
async reactivate() {
return this.call('updateReactivateContent');
},
async updatePayment() {
return this.call('updatePaymentContent');
},
async getStoreCreditHistory() {
const formData = new URLSearchParams({
cid: this.cid,
hash: this.hash,
exm: '1',
action: 'getStoreCreditHistory'
});
const response = await fetch(this.baseUrl, {
method: 'POST',
body: formData
});
return response.json();
}
};
// Usage examples
document.getElementById('pause-btn').addEventListener('click', async () => {
const result = await SUBSCRIBFY_API.pause(1);
if (result.result === 1) {
alert('Subscription paused successfully!');
location.reload();
} else if (result.result === 9) {
alert('Please wait before trying again.');
} else {
alert('Unable to pause subscription.');
}
});Liquid Form Examples
Pause Form
<form action="{{ shop.url }}/apps/subscribfy-api/store/manage-membership-dashboard" method="POST">
<input type="hidden" name="cid" value="{{ customer.id }}">
<input type="hidden" name="hash" value="{{ customer.metafields.exison.exison_hash }}">
<input type="hidden" name="exm" value="1">
<input type="hidden" name="scid" value="{{ customer.metafields.exison.customer_subscription1.scid }}">
<input type="hidden" name="action" value="updatePauseContent">
<label>Pause for:</label>
<select name="period">
<option value="1">1 month</option>
<option value="2">2 months</option>
<option value="3">3 months</option>
</select>
<button type="submit">Pause Membership</button>
</form>Cancel Form
<form action="{{ shop.url }}/apps/subscribfy-api/store/manage-membership-dashboard" method="POST">
<input type="hidden" name="cid" value="{{ customer.id }}">
<input type="hidden" name="hash" value="{{ customer.metafields.exison.exison_hash }}">
<input type="hidden" name="exm" value="1">
<input type="hidden" name="scid" value="{{ customer.metafields.exison.customer_subscription1.scid }}">
<input type="hidden" name="action" value="updateCancelContent">
<label>Reason for cancellation:</label>
<select name="reason">
<option value="Too expensive">Too expensive</option>
<option value="Not using enough">Not using enough</option>
<option value="Found alternative">Found alternative</option>
<option value="Other">Other</option>
</select>
<button type="submit">Cancel Membership</button>
</form>Gifted Memberships
When a membership is gifted, both the gift giver (parent) and recipient have access to certain actions.
Action Permissions
| Action | Parent (Gift Giver) | Recipient |
|---|---|---|
| Cancel | Yes | Yes |
| Pause | Yes | No |
| Reactivate | Yes | No |
| Update Payment | No | No |
Checking Gift Status in Liquid
{% assign parent = customer.metafields.exison.customer_subscription1_parent %}
{% assign gifts = customer.metafields.exison.customer_subscription1_gifts %}
{% if parent %}
<p>This membership was gifted to you by {{ parent.name }}.</p>
{% endif %}
{% if gifts %}
<h3>Memberships you've gifted:</h3>
<ul>
{% for gift in gifts %}
<li>{{ gift.recipient_name }} - {{ gift.status }}</li>
{% endfor %}
</ul>
{% endif %}Rate Limits
- State-changing actions (pause, cancel, reactivate): 15 second cooldown
- Item/product actions (update quantity, swap): 2 second cooldown
- Read actions (getStoreCreditHistory): No limit
When rate limited, the API returns {"result": 9}. Display a friendly message asking the customer to wait.
Best Practices
- Check metafield existence - Always verify metafields exist before rendering forms
- Handle all response codes - Provide clear feedback for success, errors, and rate limits
- Use AJAX for better UX - Avoid full page reloads with JavaScript fetch calls
- Confirm destructive actions - Add confirmation dialogs for cancel/pause actions
- Cache credit history - Store credit history changes infrequently, cache responses
- Respect gift permissions - Hide unavailable actions for gift recipients
Default Dashboard
Subscribfy provides a built-in customer dashboard that handles all these actions automatically. If you don't need a custom implementation, use the default dashboard link:
<a href="{{ shop.url }}/apps/subscribfy-api/store/manage-membership-dashboard?cid={{ customer.id }}&hash={{ customer.metafields.exison.exison_hash }}&exm=1">
Manage My Membership
</a>Questions? Contact support@subscribfy.com