Dynamics 365 → Clio Integration with Azure Service Bus & Durable Functions

From Zero to Working Demo: Power Automate → Azure Service Bus → Azure Durable Functions → Clio API

This guide walks a junior developer from a blank Azure subscription to a running integration that accepts a message, runs a Durable Function, and creates a contact in Clio via OAuth. It includes the Azure setup, the required environment variables, the project structure, and the end-to-end flow.

Architecture (high level)

  • Power Automate (or any sender) posts JSON to an Azure Service Bus queue.
  • An Azure Function with a Service Bus trigger starts a Durable Orchestration.
  • The orchestration calls an Activity that invokes Clio’s API (using OAuth tokens stored in Blob storage).

 

Prerequisites

  • Azure subscription access (Contributor on the RG is fine).
  • Clio app credentials (client id/secret) for your region (US: https://app.clio.com, Canada: https://ca.app.clio.com).
  • Visual Studio or VS Code with .NET 8 SDK; Azure Functions Core Tools (optional for local runs).

 

Step 1 — Create Azure resources (from scratch)

1.1 Resource Group

  1. Azure Portal → Resource groupsCreate → e.g., rg-clio-demo (pick your region).

1.2 Storage Account

  1. Create → Storage account → name e.g. stcliodemo (same region).
  2. Performance: Standard; Redundancy: LRS.
  3. After deployment: Storage account → Access keys → copy the connection string (you’ll need this if running locally).

1.3 Service Bus

  1. Create → Service Bus Namespace → e.g., sb-clio-demo (Standard tier recommended).
  2. Open the namespace → Shared access policies → use RootManageSharedAccessKey or create a policy with Send/Listen. Copy the Primary connection string (starts with Endpoint=sb://...).
  3. Namespace → Queues+ Queue → name: cis-clio. Defaults are fine.

1.4 Function App (.NET 8 Isolated)

  1. Create → Function App.
  2. Publish: Code; Runtime stack: .NET 8 (Isolated); OS: Windows; Plan: Consumption (Y1).
  3. Select the Storage account you created earlier.

1.5 Application Insights

  1. Keep it enabled for logging and troubleshooting.

 

Step 2 — Configure the Function App (environment variables)

In Azure, go to your Function App → Environment variables , and add these keys. 

  • AzureWebJobsStorage → (already set by Azure, leave as is)
  • ServiceBusConnectionService Bus namespace connection string (policy with Listen)
  • SERVICEBUS_QUEUE_NAMEcis-clio (or your queue)
  • CLIO_BASE_URLhttps://ca.app.clio.com (Canada) or https://app.clio.com (US)
  • CLIO_CLIENT_ID → from Clio app
  • CLIO_CLIENT_SECRET → from Clio app
  • CLIO_SCOPESread:contacts write:contacts

Optional (defaults exist):

  • CLIO_TOKEN_CONTAINERclio-oauth
  • CLIO_TOKEN_BLOBtoken.json

Click Save and then Restart the Function App.

 

Step 3 — Local development settings (optional)

If you run locally, mirror the same values in local.settings.json so Functions Core Tools can pick them up:

{
  "IsEncrypted": false,
  "Values": {
    "AzureWebJobsStorage": "<your storage connection string>",
    "FUNCTIONS_WORKER_RUNTIME": "dotnet-isolated",
    "ServiceBusConnection": "<your SB namespace connection string>",
    "SERVICEBUS_QUEUE_NAME": "cis-clio",
    "CLIO_BASE_URL": "https://ca.app.clio.com",
    "CLIO_CLIENT_ID": "<clio client id>",
    "CLIO_CLIENT_SECRET": "<clio client secret>",
    "CLIO_SCOPES": "read:contacts write:contacts"
  }
}

 

Step 4 — Project structure & responsibilities

Main C# files (you don’t need the full code here; this explains what each does):

  • Program.cs — wires up the Functions host (isolated model). Adds:
    • BlobServiceClient for token storage (uses AzureWebJobsStorage).
    • HttpClient named "Clio" (base URL = CLIO_BASE_URL).
    • TokenStore service for saving/loading ClioTokens.
  • TokenStore.cs — saves/loads ClioTokens in Blob Storage at clio-oauth/token.json (or your configured names).
  • ClioAuthFunctions.cs — two HTTP endpoints:
    • GET /api/clio/oauth/start → redirects to Clio’s Authorize page (uses CLIO_CLIENT_ID, CLIO_SCOPES, and your callback URL).
    • GET /api/clio/oauth/callback → exchanges the code at /oauth/token, then calls TokenStore.SaveAsync(...).
  • ClioIntegrationStarter.csService Bus trigger bound to your queue (%SERVICEBUS_QUEUE_NAME%). On message, it starts the orchestration ClioOrchestration with the message JSON as input.
  • ClioOrchestration.cs — contains:
    • Orchestrator function ClioOrchestration — reads input; if Action == "CreateContact", calls the activity.
    • Activity function CreateClioContact — loads tokens from TokenStore and POSTs to /api/v4/contacts.json with the Clio JSON shape (see next section).

End-to-end flow

  1. Message arrives in Service Bus queue (e.g., from Power Automate).
  2. ClioIntegrationStarter fires and starts ClioOrchestration (Durable).
  3. Orchestrator validates action and calls CreateClioContact.
  4. Activity posts to Clio; success returns a new contact ID and logs details.

 

Step 5 — The Clio contact payload (v4)

Your activity sends this JSON to /api/v4/contacts.json with header Authorization: Bearer <access_token>:

{
  "data": {
    "type": "Person",          // or "Company"
    "attributes": {
      "first_name": "Demo",
      "last_name": "FromPA",
      "email_addresses": [
        { "name": "Work", "address": "demo@example.com", "primary": true }
      ],
      "phone_numbers": [
        { "name": "Work", "number": "604-555-0101", "primary": true }
      ]
    }
  }
}

 

Step 6 — Do the OAuth handshake once

  1. Open https://<your-function>.azurewebsites.net/api/clio/oauth/start.
  2. Sign in to Clio and click Allow.
  3. Callback saves tokens to your Storage Account at clio-oauth/token.json.

If tokens expire later, run the start URL again (a refresh helper can be added as a next step).

 

Step 7 — Send a test message

Option A: Power Automate

  1. Create a flow → add Azure Service BusSend message.
  2. Use the namespace connection string (the one starting Endpoint=sb://...).
  3. Queue name: cis-clio (or your name).
  4. Message body (JSON):
{
  "Action": "CreateContact",
  "RequestedBy": "you@example.com",
  "FirstName": "Demo",
  "LastName": "FromPA",
  "Email": "demo@example.com",
  "Phone": "604-555-0101"
}

Option B: Service Bus Explorer (Portal)

  1. Namespace → Queues → your queue → Service Bus Explorer.
  2. Send Messages → paste the same JSON → Send.

 

Step 8 — Monitor & verify

  • Function App → Log stream: watch the trigger, orchestration, and activity logs (you’ll see “Created Clio contact #<id>…” on success).
  • Durable status (optional): /api/durable/instances/{instanceId} endpoint if you expose a status route.
  • Application Insights → traces for deeper diagnostics.

 

Troubleshooting quick hits

  • Service Bus connection errors: ensure ServiceBusConnection is the namespace connection string (not entity-scoped), and the queue name matches.
  • 401 from Clio: tokens expired or wrong region. Re-run /api/clio/oauth/start, confirm CLIO_BASE_URL (US vs CA).
  • 422 from Clio: payload shape/values invalid. Check that you’re sending the v4 format under data → type → attributes.
  • Nothing seems to run: confirm the Function App restarted after saving Configuration, and that Application Insights logs show the Service Bus trigger starting.

 

What you can say you’ve verified

  • Posting messages to Service Bus leads to a Durable orchestration run.
  • The activity function calls Clio’s API using OAuth tokens stored in Blob storage.
  • A new Clio Contact is created when the message payload requests it.

 

Next steps

  • Add refresh token support (renew access tokens automatically before expiry).
  • Implement upsert (find by email before create) to avoid duplicates.
  • Expand actions: ListContacts, UpdateContact, CreateMatter, etc.

 

Copy/paste checklist (env vars on Azure)

ServiceBusConnection = <SB namespace connection string>
SERVICEBUS_QUEUE_NAME = cis-clio
CLIO_BASE_URL = https://ca.app.clio.com   (or https://app.clio.com)
CLIO_CLIENT_ID = <from Clio app>
CLIO_CLIENT_SECRET = <from Clio app>
CLIO_SCOPES = read:contacts write:contacts
CLIO_TOKEN_CONTAINER = clio-oauth
CLIO_TOKEN_BLOB = token.json

That’s it. Publish the Function App, run the OAuth start URL once, send a test message, and watch the contact appear in Clio. 🚀

No comments:

Post a Comment