Agent skill

Azure Logic Apps Webhooks Skill

Receive and authenticate webhook calls from an Azure Logic Apps (or Power Automate) HTTP action. Use when building an endpoint a Logic Apps workflow calls, checking Basic authentication (Authorization: Basic base64(username:password)), Raw authentication or a shared-secret header with a constant-time compare, debugging 401s from a Logic App, or handling the workflow's JSON payload. Logic Apps does not sign requests: there is no signature header and no event types.

Install this skill

npx skills add hookdeck/webhook-skills --skill azure-logic-apps-webhooks


Covers outbound calls from the built-in HTTP action (and HTTP trigger) in Azure Logic Apps, Consumption or Standard, and the equivalent HTTP action in Power Automate. Your endpoint is the destination; the workflow decides the method, headers and body.

Not covered: the Logic Apps Request trigger (that's the workflow receiving calls, secured with SAS sig parameters or Entra ID policies), and the HTTP Webhook action's subscribe/callback protocol.

When to Use This Skill

  • How do I receive a webhook from an Azure Logic App?
  • How do I verify a Logic Apps HTTP action call? (There is no signature.)
  • How do I check Basic authentication from Logic Apps in Express, Next.js or FastAPI?
  • How do I use a shared secret header or Raw authentication with Logic Apps?
  • Why does my Logic App get 401 from my webhook endpoint?
  • Why does my Logic App keep polling my endpoint after I return 202?

Verification (core)

Logic Apps does not sign webhook payloads: no HMAC, no signature header, no timestamp. You authenticate the request with a credential configured on the HTTP action, and compare it in constant time:

HTTP action settingWhat arrives
Authentication = Basic (Username, Password)Authorization: Basic base64(username:password)
Authentication = Raw (Value)Authorization: <Value>, sent verbatim
Headers (your own name, e.g. x-logic-apps-secret)That header with your secret
const crypto = require('crypto');
// Hash first: equal-length buffers, no length leak, timingSafeEqual never throws.
const safeEqual = (a, b) => crypto.timingSafeEqual(
  crypto.createHash('sha256').update(String(a)).digest(),
  crypto.createHash('sha256').update(String(b)).digest());

function verifyBasicAuth(authorization, username, password) {
  const m = /^Basic\s+([A-Za-z0-9+/=]+)\s*$/i.exec(authorization || '');
  if (!m) return false;
  const decoded = Buffer.from(m[1], 'base64').toString('utf8');
  const sep = decoded.indexOf(':');            // first colon: passwords may contain ':'
  if (sep === -1) return false;
  const userOk = safeEqual(decoded.slice(0, sep), username);
  const passOk = safeEqual(decoded.slice(sep + 1), password);
  return userOk && passOk;                     // evaluate both, then combine
}

// Shared secret in a header (for Raw auth, the header is Authorization)
const verifySharedSecret = (value, secret) => Boolean(value && secret) && safeEqual(value, secret);

Python: hmac.compare_digest(hashlib.sha256(a).digest(), hashlib.sha256(b).digest()), and decode Basic with base64.b64decode(token, validate=True) + decoded.partition(":").

For complete handlers with tests, see examples/express/, examples/nextjs/, examples/fastapi/.

The examples fail closed (500) when no credential is configured, require every configured method to pass, and return 401 on a mismatch (Basic adds WWW-Authenticate).

Out of scope for a shared-secret check

  • Active Directory OAuth (OAuth 2.0 with Microsoft Entra ID) and Managed identity: Logic Apps sends Authorization: Bearer <Entra ID access token>. Validate it as a JWT (issuer, audience, signature via the tenant's JWKS) with an Entra ID library such as MSAL or jose, not with a string compare.
  • Client certificate: Logic Apps presents an X.509 client cert during the TLS handshake. Validate it at your TLS terminator (mTLS), not in the handler.
  • IP allowlisting: calls come from your region's Logic Apps outbound IPs (service tag LogicApps). Those ranges are shared by all Azure customers, so use them as defence in depth only.

No Event Types

Logic Apps sends no event-type header and no event field. The body is whatever you put in the action's Body (JSON by default; text, form-urlencoded and multipart also possible). Treat every request as a workflow call and, if you need routing, add your own field (for example "type": "order.approved") in the workflow.

Important Headers

HeaderDescription
AuthorizationBasic ... (Basic auth), your Raw Value, or Bearer <token> (Entra ID / managed identity)
your custom headerShared secret you add under Headers (examples default to x-logic-apps-secret)
Content-TypeSet by the action's Headers; Logic Apps drops most other Content-* headers

There's no delivery id. For idempotency, have the workflow send its run id, for example a header x-workflow-run-id: @{workflow()['run']['name']}. Retries of the same action resend the same request, so the run id plus your own payload key is a good dedupe key.

Retries, Timeouts and 202

  • The default retry policy retries 408, 429 and 5xx (and connectivity errors) up to 4 times at exponential intervals. 401/400 aren't retried.
  • Outbound request timeout: 120 s (Consumption) / 225 s default (Standard).
  • Return 200, not 202. For stateful workflows the HTTP action follows the async request-reply pattern: on 202 it polls the Location header until it gets a non-202 response.

Environment Variables

AZURE_LOGIC_APPS_USERNAME=                 # Basic auth Username
AZURE_LOGIC_APPS_PASSWORD=                 # Basic auth Password
AZURE_LOGIC_APPS_SHARED_SECRET=            # Secret in a custom header, or the full Raw Value
AZURE_LOGIC_APPS_SECRET_HEADER=x-logic-apps-secret   # Use "Authorization" for Raw auth

Local Development

# Start tunnel (no account needed)
npx hookdeck-cli listen 3000 azure-logic-apps --path /webhooks/azure-logic-apps

Reference Materials


Repository

hookdeck/webhook-skills

v0.1.0 · MIT · Updated Oct 28, 2026

View on GitHub →