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
- Azure Portal → Resource groups → Create → e.g.,
rg-clio-demo
(pick your region).
1.2 Storage Account
- Create → Storage account → name e.g.
stcliodemo
(same region). - Performance: Standard; Redundancy: LRS.
- After deployment: Storage account → Access keys → copy the connection string (you’ll need this if running locally).
1.3 Service Bus
- Create → Service Bus Namespace → e.g.,
sb-clio-demo
(Standard tier recommended). - Open the namespace → Shared access policies → use RootManageSharedAccessKey or create a policy with Send/Listen. Copy the Primary connection string (starts with
Endpoint=sb://...
). - Namespace → Queues → + Queue → name:
cis-clio
. Defaults are fine.
1.4 Function App (.NET 8 Isolated)
- Create → Function App.
- Publish: Code; Runtime stack: .NET 8 (Isolated); OS: Windows; Plan: Consumption (Y1).
- Select the Storage account you created earlier.
1.5 Application Insights
- 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)ServiceBusConnection
→ Service Bus namespace connection string (policy with Listen)SERVICEBUS_QUEUE_NAME
→cis-clio
(or your queue)CLIO_BASE_URL
→https://ca.app.clio.com
(Canada) orhttps://app.clio.com
(US)CLIO_CLIENT_ID
→ from Clio appCLIO_CLIENT_SECRET
→ from Clio appCLIO_SCOPES
→read:contacts write:contacts
Optional (defaults exist):
CLIO_TOKEN_CONTAINER
→clio-oauth
CLIO_TOKEN_BLOB
→token.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 (usesAzureWebJobsStorage
).HttpClient
named"Clio"
(base URL =CLIO_BASE_URL
).TokenStore
service for saving/loadingClioTokens
.
TokenStore.cs
— saves/loadsClioTokens
in Blob Storage atclio-oauth/token.json
(or your configured names).ClioAuthFunctions.cs
— two HTTP endpoints:GET /api/clio/oauth/start
→ redirects to Clio’s Authorize page (usesCLIO_CLIENT_ID
,CLIO_SCOPES
, and your callback URL).GET /api/clio/oauth/callback
→ exchanges thecode
at/oauth/token
, then callsTokenStore.SaveAsync(...)
.
ClioIntegrationStarter.cs
— Service Bus trigger bound to your queue (%SERVICEBUS_QUEUE_NAME%
). On message, it starts the orchestrationClioOrchestration
with the message JSON as input.ClioOrchestration.cs
— contains:- Orchestrator function
ClioOrchestration
— reads input; ifAction == "CreateContact"
, calls the activity. - Activity function
CreateClioContact
— loads tokens fromTokenStore
and POSTs to/api/v4/contacts.json
with the Clio JSON shape (see next section).
- Orchestrator function
End-to-end flow
- Message arrives in Service Bus queue (e.g., from Power Automate).
ClioIntegrationStarter
fires and startsClioOrchestration
(Durable).- Orchestrator validates action and calls
CreateClioContact
. - 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
- Open
https://<your-function>.azurewebsites.net/api/clio/oauth/start
. - Sign in to Clio and click Allow.
- 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
- Create a flow → add Azure Service Bus → Send message.
- Use the namespace connection string (the one starting
Endpoint=sb://...
). - Queue name:
cis-clio
(or your name). - 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)
- Namespace → Queues → your queue → Service Bus Explorer.
- 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
, confirmCLIO_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