Gareth Wilson Gareth Wilson

Guide to Azure Logic Apps Webhooks: Features and Best Practices

Published


Azure Logic Apps is Microsoft's workflow integration service, and a common way for a workflow to notify your systems is to call them over HTTP. An approval completes, a file lands in storage, a scheduled check finds something, and the workflow's HTTP action posts the result to your endpoint. That call is a webhook in all but name, and receiving it reliably means dealing with Logic Apps' authentication options, retry policy and asynchronous request pattern.

This guide covers what a Logic Apps HTTP call contains, how to authenticate it, how retries and the 202 polling pattern work, and the best practices for receiving Logic Apps calls in production.

What are Azure Logic Apps webhooks?

Logic Apps has no webhook subscription system of its own. Outbound calls come from the built-in HTTP action (or HTTP trigger), which Microsoft describes as the way "your workflow can send outbound calls to other services and systems". The workflow author sets every part of the request: the method, the URI, the headers, the body and the authentication. The body is usually an expression over earlier steps' outputs.

That has two consequences. First, Logic Apps adds no event type, envelope or delivery ID, so anything your receiver needs to route or deduplicate on has to be put there by the workflow. Second, Logic Apps doesn't sign requests. Authentication comes from the HTTP action's Authentication setting:

Authentication typeWhat your endpoint receives
BasicAuthorization: Basic <base64(username:password)>
RawAn Authorization header with a value the author writes, sent as is
Client certificateAn X.509 client certificate presented during the TLS handshake
Active Directory OAuthA Microsoft Entra ID access token
Managed identityA Microsoft Entra ID access token for the logic app's identity

Basic authentication is the one a shared-secret receiver can check, and it's what Hookdeck's Azure Logic Apps source type verifies.

Azure Logic Apps webhook features

FeatureDetails
SenderThe HTTP action or HTTP trigger in a Consumption or Standard workflow
MethodThe author's choice
BodyThe author's choice: JSON, text, form-urlencoded or multipart, with the Content-Type set in the action's headers
Event typesNone. Add your own field if you need to route
SignatureNone
AuthenticationBasic, Raw, Client certificate, Active Directory OAuth or Managed identity
Dropped headersAccept-* (except Accept-version), Allow, most Content-*, Cookie, Expires, Host, Last-Modified, Origin, Set-Cookie, Transfer-Encoding
RetriesDefault policy: up to 4 retries at exponential intervals, on a timeout or a 408, 429 or 5xx response
Asynchronous patternOn by default for the HTTP action in stateful workflows. A 202 makes the action poll for a result
Outbound timeout120 seconds (multitenant), 225 seconds by default (single-tenant)
Source IPsPer region, listed on Microsoft's "Limits and configuration for Azure Logic Apps" page

Common events

Logic Apps has no event catalogue. A request goes out whenever a workflow reaches its HTTP action, so the events are whatever your workflows are built to report, such as an approval completing or a file landing in storage.

Because Logic Apps adds no event type or ID, decide on a small envelope and use it in every workflow that calls your endpoint:

{
  "type": "invoice.approved",
  "run_id": "@{workflow().run.name}",
  "data": { "invoice_id": "INV-1042" }
}

type and run_id here are your own convention. The run name identifies one workflow run. A run can call your endpoint more than once, so combine it with type or a business key to deduplicate when the HTTP action's retry policy sends the same request again.

Setting up Azure Logic Apps webhooks

  1. In the workflow designer, add the HTTP action after the step that produces your data.
  2. Set Method to POST and URI to your endpoint URL.
  3. Under Headers, add Content-Type: application/json, and any header your receiver needs to route or deduplicate on, for example x-workflow-run-id with the value @{workflow().run.name}.
  4. Set Body to the JSON you want to send. Include a field that names what happened, such as "type": "invoice.approved", because Logic Apps won't add one.
  5. Under Authentication, choose Basic and enter a username and password. Store them in secured parameters or Key Vault rather than in the workflow definition.

In the workflow's JSON definition, the action looks like this:

"HTTP": {
  "type": "Http",
  "inputs": {
    "method": "POST",
    "uri": "@parameters('endpointUrl')",
    "headers": {
      "Content-Type": "application/json",
      "x-workflow-run-id": "@{workflow().run.name}"
    },
    "body": {
      "type": "invoice.approved",
      "invoiceId": "@{triggerBody()?['id']}"
    },
    "authentication": {
      "type": "Basic",
      "username": "@parameters('webhookUsername')",
      "password": "@parameters('webhookPassword')"
    }
  }
}

Don't put the credential in a header that Logic Apps removes. Microsoft says it drops the headers listed above "without showing any warning or error", so a custom header named, for example, Origin never arrives.

Securing Azure Logic Apps webhooks

Decode the Authorization header, split it at the first colon (passwords can contain colons), and compare the username and password with your stored values in constant time. Return 401 if either is wrong.

const crypto = require('crypto');
const express = require('express');

function safeEqual(a, b) {
  const left = Buffer.from(a, 'utf8');
  const right = Buffer.from(b, 'utf8');
  return left.length === right.length && crypto.timingSafeEqual(left, right);
}

function verifyBasicAuth(authorization, username, password) {
  const match = /^Basic ([A-Za-z0-9+/=]+)$/.exec(authorization || '');
  if (!match) return false;
  const decoded = Buffer.from(match[1], 'base64').toString('utf8');
  const separator = decoded.indexOf(':');
  if (separator === -1) return false;
  const userOk = safeEqual(decoded.slice(0, separator), username);
  const passOk = safeEqual(decoded.slice(separator + 1), password);
  return userOk && passOk;
}

const app = express();

app.post('/webhooks/logic-apps', express.raw({ type: '*/*' }), (req, res) => {
  const { LOGIC_APPS_USERNAME, LOGIC_APPS_PASSWORD } = process.env;
  if (!LOGIC_APPS_USERNAME || !LOGIC_APPS_PASSWORD) return res.sendStatus(500); // fail closed

  if (!verifyBasicAuth(req.get('authorization'), LOGIC_APPS_USERNAME, LOGIC_APPS_PASSWORD)) {
    return res.set('WWW-Authenticate', 'Basic').sendStatus(401);
  }

  const payload = JSON.parse(req.body.toString('utf8'));
  // Deduplicate on req.get('x-workflow-run-id'), then process asynchronously.
  res.sendStatus(200); // 200, not 202
});

The same check in Python:

import base64
import binascii
import hmac


def verify_basic_auth(authorization, username: str, password: str) -> bool:
    if not authorization or not authorization.startswith("Basic "):
        return False
    try:
        decoded = base64.b64decode(authorization[6:], validate=True).decode("utf-8")
    except (binascii.Error, UnicodeDecodeError):
        return False
    user, sep, pwd = decoded.partition(":")
    if not sep:
        return False
    user_ok = hmac.compare_digest(user.encode(), username.encode())
    pass_ok = hmac.compare_digest(pwd.encode(), password.encode())
    return user_ok and pass_ok

Basic credentials prove the caller knows the secret. They don't protect the body from tampering and they don't stop a captured request being replayed, so keep the endpoint on HTTPS.

If you use Raw authentication instead, Logic Apps sends the value you wrote as the Authorization header. Hookdeck's Logic Apps source type can verify it only if that value is itself a Basic header. Entra ID tokens and client certificates need validating as a JWT or at your TLS terminator, not with a string comparison.

Make Azure Logic Apps calls production-ready. Hookdeck Event Gateway checks the Basic credentials on every Logic Apps call, answers immediately with a 200 so the HTTP action never starts polling, and durably queues each request before your service sees it.

Azure Logic Apps webhook limitations and pain points

A 202 starts a polling loop

The Problem: Your endpoint returns 202 Accepted, and the workflow keeps calling back or never finishes the action.

Why It Happens: In stateful workflows the HTTP action follows the asynchronous operation pattern by default. A 202 tells it the work isn't finished, so it polls the URL in the Location header until it gets a non-202 response. If there's no Location header, Microsoft suggests turning off the action's Asynchronous pattern setting.

Workarounds:

  • Answer 200 once you've stored the request, even if processing happens later.
  • If you control the workflow, turn off Asynchronous pattern under the action's Networking settings.

How Hookdeck Can Help: Hookdeck answers Logic Apps with a 200 as soon as it has stored the request, then delivers it to your service, so the action completes straight away.

Retries depend on the status code

The Problem: The same call reaches your endpoint several times, or a failed call is never retried.

Why It Happens: The default retry policy resends a request that times out or returns 408, 429 or 5xx, up to 4 times, at intervals that scale by 7.5 seconds and are capped between 5 and 45 seconds. Other failures, such as a 401 or 400, aren't retried. Authors can change the policy per action, so you can't assume the default.

Workarounds:

  • Return 5xx only for failures you want retried, and 4xx for requests that will never succeed.
  • Send Retry-After on a 429 to space retries out; Logic Apps honours it.
  • Deduplicate on a run ID header so a retried call doesn't run twice.

How Hookdeck Can Help: Hookdeck accepts the call on the first attempt and runs its own retries against your service, with a schedule you configure and the ability to replay any request.

There is no event type or delivery ID

The Problem: Your receiver can't tell one workflow's calls from another's, or a retry from a new call.

Why It Happens: Logic Apps sends exactly what the author configured and nothing else.

Workarounds:

  • Put a type field (or a path per workflow) in every HTTP action.
  • Send @{workflow().run.name} in a header and combine it with a business key from the body.

How Hookdeck Can Help: Hookdeck filters can route on any header or body field you add, and its deduplication can drop repeats keyed on your run ID header.

Silent header removal

The Problem: A header you configured never arrives, and your endpoint rejects the call.

Why It Happens: Logic Apps removes Accept-* (except Accept-version), Allow, most Content-*, Cookie, Expires, Host, Last-Modified, Origin, Set-Cookie and Transfer-Encoding without a warning. Content-Type, Content-Disposition and Content-Encoding survive on POST and PUT but are dropped on GET.

Workarounds:

  • Use the Authentication setting, or a custom x- header, for credentials.
  • Use POST when you send a body.

How Hookdeck Can Help: Hookdeck records every header that actually arrived on each request, so you can see what Logic Apps sent rather than what the designer shows.

Best practices

Use Basic authentication with a long random password

Generate the password with a tool such as openssl rand -hex 32, keep it in a secured parameter or Key Vault, and compare it in constant time on the receiving side.

Send a type and a run ID

Add a type field to the body and x-workflow-run-id: @{workflow().run.name} to the headers. Your receiver can then route without guessing and deduplicate retries.

Answer 200 quickly

Return 200 as soon as you've stored the request. The HTTP action fails if your server doesn't respond within the outbound timeout (120 seconds in multitenant Logic Apps, 225 seconds by default in single-tenant), and a 202 starts the polling pattern. See why to process webhooks asynchronously.

Treat IP filtering as an extra layer

Microsoft publishes the outbound IP addresses for each region. They're shared by every logic app in the region, so use them alongside credentials, not instead of them.

Make handlers idempotent

Retries resend the same request. Store the run ID (or your own business key) before acting on a call. See our guide to webhook idempotency.

Conclusion

A Logic Apps webhook is an HTTP action that you design: Logic Apps signs nothing and adds no event type or delivery ID. Authenticate it with Basic credentials compared in constant time, put a type and a run ID into each call, answer 200 rather than 202, and expect the default policy to retry timeouts, 408, 429 and 5xx responses up to four times.

Hookdeck Event Gateway checks the credentials, answers Logic Apps immediately, and holds every call until your service has processed it.

Get started with Hookdeck for free and receive Azure Logic Apps calls reliably in minutes.


Gareth Wilson

Gareth Wilson

Product Marketing

Multi-time founding marketer, Gareth is PMM at Hookdeck and author of the newsletter, Community Inc.