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.
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.