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 setting | What 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_, 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 orjose, 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
| Header | Description |
|---|---|
Authorization | Basic ... (Basic auth), your Raw Value, or Bearer <token> (Entra ID / managed identity) |
| your custom header | Shared secret you add under Headers (examples default to x-logic-apps-secret) |
Content-Type | Set 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/400aren't retried. - Outbound request timeout: 120 s (Consumption) / 225 s default (Standard).
- Return
200, not202. For stateful workflows the HTTP action follows the async request-reply pattern: on202it polls theLocationheader 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
- references/overview.md - What Logic Apps HTTP action calls look like, payloads, retries
- references/setup.md - Configure the HTTP action, authentication and secure inputs
- references/verification.md - Basic, Raw and header-secret checks, and gotchas