top of page

QuickBooks Online x Perigee: A Dynamic Duo ๐Ÿค

QuickBooks Online - Introduction

Working with OAuth Flow can be a double-edged sword ๐Ÿ—ก๏ธ. While it's crucial for securely accessing APIs, managing tokens, authorization state, and timeouts can be challenging. Get it wrong, and you may find yourself locked out of a resource or stuck with an expired token during an important operation. QuickBooks Online (QBO) offers a comprehensive guide and an even better playground for developers ๐Ÿ› ๏ธ, especially those just starting with OAuth. But, here comes the tricky part: you have to manage independent sets of authorization and refresh tokens for each authorized company (known as a realm) within your application. Enter Perigee ๐Ÿš€. This article breaks down how to use Perigee to effortlessly manage these tokens across multiple realms.

The Core of the Process ๐Ÿง 

The following C# code snippet is executed immediately after receiving the `RealmID` and `Code` parameters from the API callback:

//Create a client pointing to the QBO Token Endpoint
var authClient = new RestClient(DiscoveryDocument.TokenEndpoint);
var authReq = new RestRequest("", Method.Post);

//Authorize the code request
authClient.UseAuthenticator(new RestSharp.Authenticators.HttpBasicAuthenticator(

//Add parameters
authReq.AddParameter("redirect_uri", iconfig.GetValue<string>("AppSettings:redirect"));
authReq.AddParameter("grant_type", "authorization_code");
authReq.AddParameter("code", code);

//Execute - Getting back an initial authorization code and refresh code. 
var rsp = authClient.ExecuteRetry<QBAuth.Tokens>(authReq, 2);

if (rsp.IsSuccessful)
    //Each credential is assigned a name: QBA- + realmID
    string qbaName = $"QBA-{realmId}";

    //Step 1) Persist an initial credential we received from the token callback
    new RestSharpCredentialStoreItem(new RestSharp.Authenticators.JwtAuthenticator(rsp.Data?.AccessToken ?? ""), DateTimeOffset.UtcNow.AddSeconds(rsp.Data?.ExpiresIn ?? 3000))
            RefreshToken = rsp.Data?.RefreshToken,
            StoreA = rsp.Data?.IdToken,
            Environment = realmId,
            Name = qbaName
    //Step 2) Register realm refresh for future usage on expiration
    CredentialStore.RegisterRefresh(qbaName, (o) =>
        var authClient = new RestClient(DiscoveryDocument.TokenEndpoint.ToString());
        var authReq = new RestRequest("", Method.Post);

        authClient.UseAuthenticator(new RestSharp.Authenticators.HttpBasicAuthenticator(iconfig.GetValue<string>("AppSettings:client_id")!, iconfig.GetValue<string>("AppSettings:client_secret")!));
        authReq.AddParameter("redirect_uri", iconfig.GetValue<string>("AppSettings:redirect")!);
        authReq.AddParameter("grant_type", "refresh_token");
        authReq.AddParameter("refresh_token", CredentialStore.GetRefreshToken(qbaName));
        var rsp = authClient.ExecuteRetry<QBAuth.Tokens>(authReq, retries: 1);

        if (rsp.IsSuccessful)
            //Get previous expired credential using peek to pull environment from last run...
            var expCred = CredentialStore.PeekCredential(qbaName);

            return new RestSharpCredentialStoreItem(new RestSharp.Authenticators.JwtAuthenticator(rsp.Data?.AccessToken ?? ""), DateTimeOffset.UtcNow.AddSeconds(rsp.Data?.ExpiresIn ?? 3000))
                    RefreshToken = rsp.Data?.RefreshToken,
                    StoreA = rsp.Data?.IdToken,
                    Environment = expCred?.Environment ?? "",
            return new FaultedCredentialStoreItem($"Couldn't refresh token for realm {qbaName}, {rsp.Content}");

Re-Register the Callback on Application Startup ๐Ÿ”„

Since each realm has its own set of authorization details, it's important to re-register the callback functions for each realm when the application restarts:

foreach (var cred in CredentialStore.GetCredentialsByPredicate(f => f.Name.StartsWith("QBA-")))
    var QBAName = cred.Name;
    //TODO: Call the register function again listed above. It would be wise to encapsulate that mathod to use in multiple places!

Making Authorized Realm Calls: Simpler Than Ever ๐Ÿฅณ

Thanks to our prior setup with Perigee, making authorized realm calls is now a walk in the park ๐ŸŒณ. The system ensures that you get a freshly refreshed authorization code each time you hit an endpoint. Plus, it authorizes the call pre-emptively, reducing the risk of a failure midway through a process.

var realmID = 123456789;

//New client (this is using sandbox, you *should* be querying the discovery endpoint for this)
client = new RestClient(new RestClientOptions(""));

//Use the credential authenticator here, with the QBA-Realm key! Voila!!!
client.UseAuthenticator(new CredentialAuthenticator($"QBA-{realmID}"));

var rsp = client.ExecuteRetry<CompanyInfo.QueryResponse>($"/v3/company/{realmID}/query?query=select * from CompanyInfo", retries: RetryCount);

In conclusion, while OAuth can often feel like a labyrinth of tokens and authorizations, Perigee simplifies the process, making it far easier and more reliable. Happy coding from the Perigee Team! ๐ŸŽ‰


Email Icon

Follow all the exciting developments of the Perigee Revolution by joining our Perigee Email List today! Or simply email us at

Youtube icon

Watch the latest disrupting videos at Perigee Software - YouTube

twitter icon

Tweet at us at @PerigeeNinja

documentation icon

Get full documentation with ready-to-run examples at Perigee Documentation

Facebook icon

Like us on Facebook at Perigee Facebook Page

11 views0 comments

Recent Posts

See All
bottom of page