Advanced
Signing Billing Portal Links
We recommend using the Boathouse API to fetch a billing portal link for your logged-in user. But here we will show you how to manually create that link for a user.
Before you begin make sure:
- You have the Portal ID and Secret from your Boathouse dashboard.
- You have the associated Paddle Customer ID for your user.
Advanced Portal Features
Once you understand the basics of signing portal links, you can enhance the customer experience with deep linking functionality. Deep linking allows you to create direct links to specific actions like plan changes, cancellations, or billing updates.
The billing portal takes 5 parameters in the query string.
- Parameter "c": The Paddle Customer ID.
- Parameter "e": A UNIX timestamp value after which the link is invalid. (A maximum expiration of 1 hour is recommended.)
- Parameter "p": The Portal ID.
- Parameter "r": The URL to send the user when they finish using the billing portal.
- Parameter "s": An url encoded HMAC signature hash for the first four values calculated as follows:
Create a query string of the first four values (URL encode the returnUrl and lower case it):
c={customerID}&e={expiry}&p={portalID}&r=
{returnUrl}
Sign this string with an HMAC SHA256 hash algorithm and the portal secret from your Boathouse dashboard.
Then you can redirect your user to
https://my.boathouse.co/portal?c={customerID}&e={expiry}&p={portalID}&r={returnUrl}&s={signature}
Examples
C# / .NET
using System.Security.Cryptography;
using System.Text;
using System.Web;
public class PortalHmac
{
private static readonly DateTime Epoch = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc);
public static string GetSignature(
string portalSecret,
Guid portalID,
string customerID,
string returnUrl,
long expiresAfterUnixTimestamp)
{
byte[] keyByte = Encoding.UTF8.GetBytes(portalSecret);
var query = GetQueryString(portalID, customerID, returnUrl, expiresAfterUnixTimestamp);
byte[] messageBytes = Encoding.UTF8.GetBytes(query);
using (var hmacsha256 = new HMACSHA256(keyByte))
{
byte[] hashmessage = hmacsha256.ComputeHash(messageBytes);
return Convert.ToBase64String(hashmessage);
}
}
public static long ConvertToUnixTimestamp(DateTime value)
{
TimeSpan elapsedTime = value - Epoch;
return (long)elapsedTime.TotalSeconds;
}
public static string GetQueryString(
Guid portalID,
string customerID,
string returnUrl,
long expiresAfterUnixTimestamp)
{
var returnUrlEncoded = HttpUtility.UrlEncode(returnUrl).ToLower();
return $"c={customerID}&e={expiresAfterUnixTimestamp}&p={portalID}&r={returnUrlEncoded}";
}
public static string GetQueryStringWithSignature(
string portalSecret,
Guid portalID,
string customerID,
string returnUrl,
long expiresAfterUnixTimestamp)
{
var signatureEncoded = HttpUtility.UrlEncode(
GetSignature(portalSecret, portalID, customerID, returnUrl, expiresAfterUnixTimestamp)
);
return $"{GetQueryString(portalID, customerID, returnUrl, expiresAfterUnixTimestamp)}&s={signatureEncoded}";
}
}
Node.JS
const crypto = require('crypto')
const createPortalUrl = (customerId) => {
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')
return `https://my.boathouse.co/portal?${query}&s=${encodeURIComponent(
signature,
)}`
}
PHP
$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));
$portalUrl = "https://my.boathouse.co/portal?{$query}&s=" . urlencode($signature);
Others
For other languages see here for examples on how to calculate the signature.