CA Trusted Canadian solution · Developed in Canada, operated in private Tier III data centers

Install the Secure Exchanges relay in your Azure subscription

From an empty Azure subscription to a working HTTP endpoint, callable from any webhook, automation platform or application. About ten minutes, nothing to install on your machine.

This guide takes you from an empty Azure subscription to a working HTTP endpoint you can call from anything that can send a JSON POST: a webhook, an automation platform, your own code.

Allow about ten minutes. You install nothing on your machine — everything happens in the browser.

What you need

  1. Your three Secure Exchanges licence credentials: serial number, API user and API password. Three GUIDs, shaped like a1b2c3d4-e5f6-4a7b-8c9d-0e1f2a3b4c5d. If you are an administrator, or if you have access to user management, you can retrieve them yourself: open User management, find the user and click the key icon in the Actions column.
  2. An Azure subscription, with the Contributor role so you can create resources.
  3. Which environment your licence belongs to: production or preview. If unsure, try production first; if the test in step 7 returns an authorisation error, redo step 3 and pick preview.

Your credentials live in the configuration of your own Function App. They are never handed to the tool calling the relay, and never reach Secure Exchanges outside the usual encrypted protocol.

Step 1 — Open the template

Click the button:

Deploy to Azure

The Azure portal opens a creation form. If you are not signed in yet, Azure asks for your credentials first.

Step 2 — Choose where to install

At the top of the form:

  • Subscription: the one that will carry the cost.
  • Resource group: click Create new and name it, for example rg-secure-exchanges. A dedicated group makes it easy to remove everything later.
  • Region: pick the one closest to you.

Step 3 — Provide your credentials

The form first asks how you want to supply them.

Option A — type them in

Three fields, one GUID each: serial number, API user, API password. All three are masked as you type. The form still checks the format live, so a truncated paste or a value that is not a GUID is flagged right away — you never have to proofread a hidden field.

The three fields are declared as secure parameters: Azure does not record them in your resource group's deployment history, and no later look at that deployment brings them back. They are written to your Function App configuration, where they stay readable by anyone allowed to view that resource's application settings. If your policy rules that out too, use option B — the secret then never leaves your vault.

Option B — read them from your Azure Key Vault

Pick Read them from Azure Key Vault, then:

  1. Select your vault from the dropdown.
  2. Type the three secret names. Azure offers no picker for secret names, so copy them exactly as they appear in your vault. The suggested values — se-serial, se-api-user, se-api-password — are only suggestions.

Your secrets then appear neither in the form nor in the deployment history: only a reference does. The app resolves the values at start-up using its managed identity.

One extra step after the deployment: let the app read the vault. In Cloud Shell, replacing the three names:

az role assignment create --role "Key Vault Secrets User" --assignee-object-id $(az functionapp identity show --resource-group <your-group> --name <your-app> --query principalId -o tsv) --assignee-principal-type ServicePrincipal --scope $(az keyvault show --name <your-vault> --query id -o tsv)

Then restart the app. Until that permission is granted, the app cannot resolve the references and the test in step 7 answers 500 Relay is not configured — a missing permission, not a typo.

This option requires Owner or User Access Administrator on the vault. A plain Contributor role cannot create a role assignment.

The remaining fields

Field What to put
Function App name Leave empty and a unique name is generated. If you want a specific one, letters, digits and hyphens only
Secure Exchanges environment Production, or Preview if your licence lives on the preview environment
Default language The language your recipients see when a call does not specify one. Every call can override it with the culture field

The Sizing tab holds memory and instance ceiling. The defaults suit a few hundred calls a month.

Resources are created in the region of the resource group chosen in step 2.

Step 4 — Create

Click Review + create, then Create. Azure takes about two minutes to provision:

  • a storage account,
  • a Flex Consumption plan, billed per use,
  • the Function App itself, with your credentials already in place.

Step 5 — Install the code

The infrastructure is ready but the app is still empty. One command installs it.

  1. When the deployment finishes you are already on its page. In the left menu, under Settings, click Outputs. (If you closed that page: resource group → SettingsDeployments → the listed deployment → Outputs.)
  2. Copy the value of step1_installationCommand.
  3. At the top of the portal, click the Cloud Shell icon (>_). Choose Bash if asked. On first use Azure offers to create a small storage account: accept.
  4. If your account has several subscriptions, select the right one first:

    az account set --subscription "<your subscription name or id>"

    To list them all:

    az account list --output table
  5. Paste the command and press Enter.

The install takes a minute. Wait for Deployment was successful.

Step 6 — Get your URL

Still in the deployment Outputs, copy step2_relayUrlWithKey. It looks like:

https://se-relay-xxxxx.canadaeast-01.azurewebsites.net/api/enveloppe?code=<key>

This URL already carries the access key. Treat it like a password: whoever holds it can create envelopes and send messages on your licence.

The step4_accessKey output gives the key on its own. Use it to build the other routes — the same key works for /api/enveloppe, /api/message and /api/version.

The step3_testCommand output is a ready-made curl against /api/version, key included — the shortest way to confirm the relay answers.

Step 7 — Check that it works

In the same Cloud Shell, replacing the URL and the destination address:

curl -s -X POST "<your URL from step 6>" -H "Content-Type: application/json" -d '{"subject":"Test","destination":"you@yourcompany.com"}'

Expected answer:

{"status":200,"url":"https://www.secure-exchanges.com/...","repId":"...","error":null}

The url field is the envelope link. Open it in your browser to see what your recipient will see.

If you get something else:

Answer Cause Fix
401 with no JSON body The URL was truncated when copied Copy step2_relayUrlWithKey in full
{"status":500,...,"error":"Relay is not configured"} A credential was not saved Function App → Settings → Environment variables, check SE_SERIAL, SE_API_USER, SE_API_PASSWORD
{"status":502,...} Licence refused, usually the wrong environment Switch SE_ENDPOINT to the other address (production ↔ preview)
{"status":403,...} The licence has no API access Contact Secure Exchanges
404 The code is not installed Redo step 5

Operations reference

All three operations share the same URL and the same key: only the end of the path changes.

Route What it does
POST /api/enveloppe Creates an envelope link: your recipient sends documents back to you
POST /api/message Sends a secure message to one or more recipients
GET /api/version Reports your version and whether an update is available

All expect Content-Type: application/json. The HTTP status of the answer always equals the status field in the body: rely on whichever you prefer.

POST /api/enveloppe

Creates a link you send to someone so they can send documents back to you, securely, without subscribing to anything or installing anything.

Required: subject and destination.

Field Type Purpose
subject text Envelope subject, visible to the recipient
destination text A single email. An envelope is a personal reply link: for several people, call the endpoint once per recipient
culture text fr-CA or en-CA, for this call only. Otherwise the deployment default
callBackParameters text Free content returned to you in the API callback, handy to identify the file or client
replyToAPI boolean true to receive the reply through your API callback instead of by email. Default: false
notifyWhenRead boolean You are notified when it is opened. Default: true
replyExpirationHours integer How long the link lives, in hours
maximumReplyOpenTime integer Maximum number of opens
authorizedExtensions list Allowed extensions, for example [".pdf", ".jpg"]. Absent = no restriction
curl -s -X POST "<your URL>/api/enveloppe?code=<key>" -H "Content-Type: application/json" -d '{"subject":"Requested documents","destination":"client@example.com","replyExpirationHours":72,"authorizedExtensions":[".pdf"]}'
{
  "status": 200,
  "url": "https://www.secure-exchanges.com/...&enveloppe=true",
  "repId": "1b2c3d4e-....",
  "error": null
}

url is the link to pass on. repId identifies the expected reply: keep it if you use the API callback, it lets you match the reply to the right file.

POST /api/message

Sends a secure message. Each recipient gets their own link, all share the password. Secure Exchanges sends the email itself: you provide no mail server.

Required: subject, message, and at least one entry in recipients.

Field Type Purpose
subject text Email subject
message text Message body, HTML or plain text
recipients list The recipients: [{"email":"...","phone":"+15145551234"}]. At least one is required. Under msgSMSOnly, email becomes optional: a recipient reached by SMS alone is accepted
password text Password protecting the message, shared by all recipients
culture text fr-CA or en-CA, for this call only
sendMode text onlyEmail by default. msgSMSOnly, msgSMSCodeEmail, msgEmailCodeSms require a phone number for every recipient

A recipient the relay cannot reach fails the call and names its index, for example recipients[1]: email is required. Nothing is dropped quietly: either the whole batch goes out, or you get an error pointing at the line to fix.

| notifyWhenRead | boolean | You are notified when it is opened. Default: true |

| clearTextNotification | boolean | Open notification in clear text rather than encrypted. Default: false |

| maximumOpenTime | integer | Opens before destruction, 1 to 99. Default: 5 |

| expirationMinutes | integer | How long the message lives. Default: 50400 |

| senderName | text | Name shown as the sender |

| callBackParameters | text | Free content returned when the message is read. Nothing confidential here |

curl -s -X POST "<your URL>/api/message?code=<key>" -H "Content-Type: application/json" -d '{"subject":"Your documents","message":"<p>Hello</p>","recipients":[{"email":"client@example.com"}],"password":"a-shared-secret"}'
{
  "status": 200,
  "recipients": [
    {
      "email": "client@example.com",
      "status": 200,
      "url": "https://www.secure-exchanges.com/...",
      "messageId": "xH25g0f2...",
      "trackingId": "4ea4a752-1972-4c76-9d82-63dda214a32f",
      "openingCode": null
    }
  ],
  "error": null
}

The top-level status is 200 when the call went through; also check each recipient's status, one of them can be refused while the others succeed. trackingId is what you use to track or delete the message.

Attachments are not exposed by this route: shipping binary files does not fit in a plain JSON POST. Contact us if you need them.

GET /api/version

No body, no parameters. Reports what runs on your side and whether an update exists.

curl -s "<your URL>/api/version?code=<key>"
{
  "status": 200,
  "version": "1.1.0",
  "publishedVersion": "1.2.0",
  "updateAvailable": true,
  "operations": ["POST /api/enveloppe", "POST /api/message", "GET /api/version"],
  "error": null
}

It is also the safest test after an install: it creates nothing and sends nothing.

Status codes, common to all three routes

Code Meaning What to do
200 Success
400 Invalid body or missing required field The error field names the problem
401 Key missing or invalid Copy the full URL from step 6
403 Secure Exchanges refused the licence Check the API access on your licence
500 Relay misconfigured, or internal error Check the credentials in the environment variables
502 No answer from Secure Exchanges Usually the wrong environment: production against preview

Step 8 — You are ready

Your relay is live. You now have ordinary HTTP URLs you can call from anywhere that sends a JSON POST: a webhook, an automation platform, a CRM, a script, your own application.

What you want to do Where to point
Create a reply link <your URL>/api/enveloppe?code=<key>
Send a secure message <your URL>/api/message?code=<key>
Check the installed version <your URL>/api/version?code=<key>

The recipe is the same everywhere: POST (except /api/version, which is a GET), the Content-Type: application/json header, and the JSON body described in the previous step. The answer comes back as JSON: the link sits in url for an envelope, in recipients[0].url for a message.

If your tool asks you to pick a request type, choose the one that lets you write the body yourself — often called Custom Request or HTTP. Avoid modes that copy trigger fields in automatically: the body has to be exactly the expected JSON.

In the following Zap steps, the link sits in the url field of the answer, ready to drop into an email, an SMS or a CRM record. For a message send, it is recipients[0].url.

Make and Power Automate are configured the same way: an HTTP module, method POST, the full URL, the Content-Type header and the JSON body.

What it costs

Three items, all billed per use. With no calls, the relay costs practically nothing.

Item What is billed Order of magnitude
Function App (Flex Consumption) Execution time in GB-seconds, plus the number of executions The main item, and it stays tiny
Storage account A few megabytes: the relay package and its metadata A few cents a month
Network egress The calls to Secure Exchanges, a few kilobytes each Negligible

How to estimate it for your volume. A call takes one to three seconds: the relay sets up an encrypted exchange with Secure Exchanges, then waits for its answer. At 512 MB per instance, the default, that is roughly 0.5 to 1.5 GB-seconds per call.

For 1,000 envelopes a month: in the order of 500 to 1,500 GB-seconds and 1,000 executions. For 10,000 calls, multiply by ten. Either way you stay far below the amounts that justify budget monitoring.

For an exact figure, use the Azure pricing calculator: rates vary by region, and Microsoft applies a monthly free grant on consumption plans whose amount changes over time. Look up Azure Functions, pick the Flex Consumption plan, and enter the GB-seconds and execution count above.

What would push the bill up, if anything: raising the memory per instance without needing it — the relay never uses more than 512 MB — or turning on always-ready instances, which our template leaves off. The instance ceiling costs nothing by itself: it caps scale-out, it reserves no capacity.

Tighten access (optional)

The key in your URL already protects the endpoint and is revoked in one click: Function App → FunctionsCreateEnveloppeFunction keys. If you want a second key, independent of the Azure one:

  1. Function App → SettingsEnvironment variables → add RELAY_EXTRA_API_KEY with a long random value.
  2. In your tool, add the x-relay-key header carrying that same value.

Any call without that header is then refused.

Step 9 — Know whether you are up to date, and update

Secure Exchanges publishes new versions of the relay: fixes, new operations. Your installation does not update itself, but it can tell you where it stands.

Check your version

Take your URL from step 6, change the end of the path to /api/version, and open it in your browser or in Cloud Shell:

curl -s "https://<your-app>.azurewebsites.net/api/version?code=<your-key>"
{
  "status": 200,
  "version": "1.1.0",
  "publishedVersion": "1.2.0",
  "updateAvailable": true,
  "operations": ["POST /api/enveloppe", "POST /api/message", "GET /api/version"],
  "error": null
}
Field What it tells you
version What is running on your side right now
publishedVersion The latest version published by Secure Exchanges
updateAvailable true: an update is waiting. false: you are current
operations The operations available in your version

If publishedVersion and updateAvailable are null, your app could not reach the published version file. Nothing is broken: everything else works, only the comparison is unavailable.

Run the update

One command, the same as step 5. Find it in the portal: your resource group → SettingsDeployments → your deployment → Outputsstep1_installationCommand.

Paste it into Cloud Shell and wait for Deployment was successful.

Nothing else to do: your credentials, settings, keys and URL are all preserved. No button to click again, no form to fill in — only the code is replaced.

Call /api/version afterwards: version should now match publishedVersion.

If the update brings a new operation, it is reachable straight away with the same key. Keep your URL and change only the end of the path.

Remove everything

Delete the resource group created in step 2: that wipes the Function App and the storage in one go. Nothing is left behind on the Azure side, and your Secure Exchanges licence is unaffected.

Subscribe to the Secure Exchanges newsletter

Captcha