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:

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.

Previous
Anonymous Checkout