Advanced
Deep Linking into the Paddle Customer Portal
The customer portal now supports deep linking functionality that allows you to create direct links to specific actions and pages within the portal. This feature is particularly useful for:
- Marketing campaigns directing customers to upgrade/downgrade plans
- Support teams helping customers navigate directly to specific features
- Email campaigns with targeted calls-to-action
- Reducing friction in the customer journey by minimizing clicks
We recommend using the Boathouse API to fetch a billing portal link for your logged-in user where you can then append the deep-linking parameters below (they do not affect the signature). But here we will show you how to manually create that link for a user.
Prerequisites
Before using deep linking features, make sure you understand how to create signed billing portal links. Deep linking parameters are added to the basic signed portal URL.
Base Portal URL Structure
The basic customer portal URL with deep linking parameters follows this structure:
https://my.boathouse.co/portal?p={portalId}&c={customerId}&s={signature}&e={expiresTimestamp}&r={returnUrl}&{deepLinkParameters}
Deep Linking Parameters
Subscription Selection (subscription_id
)
Directly select a specific subscription for actions:
https://my.boathouse.co/portal?c={customerID}&e={expiry}&p={portalID}&r={returnUrl}&s={signature}&subscription_id={subscriptionId}
Use Cases:
- Direct customer to manage a specific subscription
- Open change plan UI for a particular subscription
- Useful when customer has multiple subscriptions
Behavior:
- Opens the portal with the specified subscription selected
- If used alone, opens the change plan interface for that subscription
- If subscription ID doesn't exist or doesn't belong to the customer, it's ignored
Target Price Selection (target_price_id
)
Automatically navigate to plan change with a specific price pre-selected:
https://my.boathouse.co/portal?c={customerID}&e={expiry}&p={portalID}&r={returnUrl}&s={signature}&target_price_id={targetPriceId}
Use Cases:
- Marketing campaigns for specific plan upgrades
- Promotional emails for plan changes
- Upsell/downsell workflows
- Reducing friction for planned migrations
Behavior:
- Automatically opens the change plan interface
- Pre-selects the specified price/plan
- Navigates directly to the confirmation stage
- Shows preview of pricing changes immediately
- If no
subscription_id
is provided, defaults to the first available subscription
Action Parameter (a
)
Control which panel opens in the portal interface:
https://my.boathouse.co/portal?c={customerID}&e={expiry}&p={portalID}&r={returnUrl}&s={signature}&a={action}
Available Actions:
change_plan
- Opens the plan change interfaceconfirm_change_plan
- Opens plan change with confirmation (use withtarget_price_id
)pause_subscription
- Opens the pause subscription interface (or resume if already paused)cancel_subscription
- Opens the cancel subscription interfacechange_quantity
- Opens the change quantity/licenses interfacechange_billing_information
- Opens the billing information update interfacelist_invoices
- Shows only the invoices panel (can be filtered bysubscription_id
)cancel_downgrade
- Opens confirmation to cancel a scheduled downgrade
Case Sensitivity
The action parameter uses snake_case enum values. ASP.NET Core's model binding is case-insensitive, so change_plan
, Change_Plan
, or CHANGE_PLAN
will all work.
Combined Deep Linking
You can combine parameters for precise control:
https://my.boathouse.co/portal?c={customerID}&e={expiry}&p={portalID}&r={returnUrl}&s={signature}&subscription_id={subscriptionId}&target_price_id={targetPriceId}&a=confirm_change_plan
This will:
- Select the specific subscription
- Open the change plan interface
- Pre-select the target price
- Show the confirmation screen with pricing preview
Implementation Examples
Upgrade Campaign
Create a link for customers to upgrade to your "Pro Annual" plan:
https://my.boathouse.co/portal?p=...&target_price_id=pri_professional_annual_2024&a=confirm_change_plan
When clicked, this will:
- Open the customer portal
- Automatically select their subscription (or first one if multiple)
- Pre-select the Professional Annual plan
- Show the pricing change confirmation immediately
Support Team Assistance
Help a customer with a specific subscription change:
https://my.boathouse.co/portal?p=...&subscription_id=sub_XYZ789&target_price_id=pri_basic_monthly_2024&a=confirm_change_plan
This provides precise control to:
- Target the exact subscription (useful for customers with multiple)
- Pre-select the plan they discussed with support
- Minimize confusion and steps needed
Simple Plan Change Navigation
Just open the change plan interface for easy browsing:
https://my.boathouse.co/portal?p=...&subscription_id=sub_XYZ789&a=change_plan
This will:
- Open the change plan interface for the specified subscription
- Allow the customer to browse available plans
- No pre-selection, giving full control to the customer
Pause or Cancel Subscription
Direct link to pause a subscription:
https://my.boathouse.co/portal?p=...&subscription_id=sub_XYZ789&a=pause_subscription
If the subscription is already paused, this will show a resume screen instead.
Direct link to cancel a subscription:
https://my.boathouse.co/portal?p=...&subscription_id=sub_XYZ789&a=cancel_subscription
Update Billing Information
Direct link to update payment method:
https://my.boathouse.co/portal?p=...&subscription_id=sub_XYZ789&a=change_billing_information
Cancel Scheduled Downgrade
Direct link to cancel a scheduled downgrade:
https://my.boathouse.co/portal?p=...&subscription_id=sub_XYZ789&a=cancel_downgrade
Use Case: When a customer has scheduled a downgrade but you want to offer them an easy way to keep their current plan (e.g., in a retention email campaign).
View Invoices Only
Direct link to view all invoices:
https://my.boathouse.co/portal?p=...&a=list_invoices
Filter invoices for a specific subscription:
https://my.boathouse.co/portal?p=...&subscription_id=sub_XYZ789&a=list_invoices
The list_invoices
action displays only the invoices panel without any subscription information. When subscription_id
is provided, it filters the invoices to show only those related to the specified subscription.
Code Examples
C# / .NET with Deep Linking
using System.Security.Cryptography;
using System.Text;
using System.Web;
public class DeepLinkedPortal : PortalHmac
{
public static string CreateUpgradeLink(
string portalSecret,
Guid portalID,
string customerID,
string returnUrl,
string targetPriceId,
long expiresAfterUnixTimestamp)
{
var baseQuery = GetQueryString(portalID, customerID, returnUrl, expiresAfterUnixTimestamp);
var signature = HttpUtility.UrlEncode(
GetSignature(portalSecret, portalID, customerID, returnUrl, expiresAfterUnixTimestamp)
);
return $"https://my.boathouse.co/portal?{baseQuery}&s={signature}&target_price_id={targetPriceId}&a=confirm_change_plan";
}
public static string CreateSubscriptionManagementLink(
string portalSecret,
Guid portalID,
string customerID,
string returnUrl,
string subscriptionId,
string action,
long expiresAfterUnixTimestamp)
{
var baseQuery = GetQueryString(portalID, customerID, returnUrl, expiresAfterUnixTimestamp);
var signature = HttpUtility.UrlEncode(
GetSignature(portalSecret, portalID, customerID, returnUrl, expiresAfterUnixTimestamp)
);
return $"https://my.boathouse.co/portal?{baseQuery}&s={signature}&subscription_id={subscriptionId}&a={action}";
}
}
Node.js with Deep Linking
const crypto = require('crypto')
const createDeepLinkedPortalUrl = (customerId, deepLinkParams = {}) => {
const expiration = Math.floor(Date.now() / 1000) + 60 * 60 // 1 hour
const returnUrl = encodeURIComponent('https://example.com').toLowerCase()
const query = `c=${customerId}&e=${expiration}&p=${boathousePortalId}&r=${returnUrl}`
const signature = crypto
.createHmac('sha256', boathouseSecret)
.update(query)
.digest('base64')
let url = `https://my.boathouse.co/portal?${query}&s=${encodeURIComponent(
signature,
)}`
// Add deep linking parameters
if (deepLinkParams.subscription_id) {
url += `&subscription_id=${deepLinkParams.subscription_id}`
}
if (deepLinkParams.target_price_id) {
url += `&target_price_id=${deepLinkParams.target_price_id}`
}
if (deepLinkParams.action) {
url += `&a=${deepLinkParams.action}`
}
return url
}
// Usage examples:
const upgradeLink = createDeepLinkedPortalUrl('c123', {
target_price_id: 'pri_professional_annual_2024',
action: 'confirm_change_plan',
})
const pauseLink = createDeepLinkedPortalUrl('c123', {
subscription_id: 'sub_XYZ789',
action: 'pause_subscription',
})
PHP with Deep Linking
function createDeepLinkedPortalUrl($customerId, $deepLinkParams = []) {
global $boathousePortalId, $boathouseSecret;
$expiration = time() + 60 * 60; // 1 hour
$returnUrl = strtolower(urlencode('https://example.com'));
$query = "c={$customerId}&e={$expiration}&p={$boathousePortalId}&r={$returnUrl}";
$signature = base64_encode(hash_hmac('sha256', $query, $boathouseSecret, true));
$url = "https://my.boathouse.co/portal?{$query}&s=" . urlencode($signature);
// Add deep linking parameters
if (isset($deepLinkParams['subscription_id'])) {
$url .= "&subscription_id=" . $deepLinkParams['subscription_id'];
}
if (isset($deepLinkParams['target_price_id'])) {
$url .= "&target_price_id=" . $deepLinkParams['target_price_id'];
}
if (isset($deepLinkParams['action'])) {
$url .= "&a=" . $deepLinkParams['action'];
}
return $url;
}
// Usage examples:
$upgradeLink = createDeepLinkedPortalUrl('c123', [
'target_price_id' => 'pri_professional_annual_2024',
'action' => 'confirm_change_plan'
]);
$cancelLink = createDeepLinkedPortalUrl('c123', [
'subscription_id' => 'sub_XYZ789',
'action' => 'cancel_subscription'
]);
Security Considerations
Important Security Note
The deep linking parameters (subscription_id
, target_price_id
, and a
) are NOT included in the HMAC signature calculation. This is intentional and secure because:
- Authentication remains intact: The core authentication parameters (portal ID, customer ID, expiration) are still validated
- Flexibility: The same signed URL can be reused with different navigation parameters
- No security risk: These parameters only control navigation within the already-authenticated session
- Customer isolation: Customers can only see and modify their own subscriptions
Parameter Validation
The system validates:
- Subscription ID must belong to the authenticated customer
- Target price ID must be a valid, active price in the portal
- If invalid parameters are provided, they are silently ignored
- The portal falls back to default behavior for invalid parameters
This ensures that even if someone manipulates the deep linking parameters, they cannot access or modify data that doesn't belong to the authenticated customer.