<@ULVA73B9P> I'm trying to trigger a prefect webho...
# ask-marvin
a
@Marvin I'm trying to trigger a prefect webhook with an azure storage event using eventgrid but i'm getting the error "Webhook validation handshake failed"
m
thought for 319.1 seconds
That error means Azure couldn’t complete the initial endpoint validation for your webhook. Prefect Cloud webhooks do support Azure Event Grid’s validation handshake, so this usually comes down to URL, auth, or schema settings. Quick checklist to fix “Webhook validation handshake failed”: 1) Use the exact Prefect Cloud webhook URL - Copy/paste the URL from the Webhooks page in Prefect Cloud (no extra query params or edits). - Make sure the webhook is Active. 2) If your webhook requires authentication, add the header in Event Grid - If your workspace enforces webhook auth or you attached a service account to the webhook, Event Grid must include:
Copy code
Authorization: Bearer <PREFECT_API_KEY>
- In the Azure Portal, add this as an Additional/Delivery header on the Event Subscription (it must be present on the validation request too). - If you see 401/403 in the Event Grid “last attempt” details, it’s an auth issue. 3) Delivery schema - Event Grid Schema and CloudEvents v1.0 are both supported by Prefect Cloud webhooks. - If validation keeps failing, try switching the subscription to CloudEvents v1.0 and re-validate. 4) Sanity check with curl - If no auth on the webhook:
Copy code
curl -i -X POST "<your-prefect-webhook-url>"
Expect 204 No Content. - If the webhook requires auth:
Copy code
curl -i -X POST "<your-prefect-webhook-url>" -H "Authorization: Bearer <PREFECT_API_KEY>"
Expect 204 No Content. - 401/403 = add the Authorization header to the Event Grid subscription. 404 = wrong URL or webhook deleted. 5) Check Event Grid diagnostics - In the Event Subscription blade, look at the validation attempt status code/response: - 401/403 → missing/invalid Authorization header - 404 → wrong endpoint - 5xx → temporary issue; retry - TLS/cert errors → ensure your environment allows outbound to Prefect Cloud Minimal Prefect webhook templates for Azure payloads (after validation succeeds): - If you selected CloudEvents v1.0 in Event Grid:
Copy code
{{ body|from_cloud_event(headers) }}
- If you selected Event Grid Schema (Azure posts an array):
Copy code
{% set ev = body[0] if body is iterable else body %}
{
  "event": "azure.eventgrid",
  "resource": {
    "prefect.resource.id": "azure.{{ ev.eventType }}",
    "azure.subject": "{{ ev.subject }}"
  },
  "payload": {{ ev.data|tojson }}
}
Docs for reference: - Prefect webhooks overview and Azure Event Grid notes: Webhooks (Prefect 3.x) - How to create and use a webhook: Create a webhook (Prefect Cloud) - Azure endpoint validation (what Event Grid expects): Azure: Endpoint validation Could you share: - Which delivery schema you chose (Event Grid schema vs CloudEvents v1.0)? - Whether your Prefect webhook requires auth (and if you added the Authorization header to the Azure subscription)? - The status code/response shown in the Event Grid validation error details? - A quick curl test result against your webhook (redact IDs/keys)? With those details I can pinpoint the exact cause and get you unblocked quickly.
a
@Marvin using event grid schema resultsa in the error "Http POST request failed with response code Unknown." using cloud events 1.0 schema results in the error "Http OPTIONS request failed with response code InternalServerError." a curl requests is successful. there is no auth on the webhook
m
thought for 272.8 seconds
Thanks for the details — that helps a lot. What your errors suggest: - CloudEvents 1.0: Event Grid sends a preflight OPTIONS with WebHook-Request-Origin. Getting “InternalServerError” there means the endpoint returned a 5xx to the OPTIONS handshake. - Event Grid schema: During subscription creation, Event Grid POSTs a SubscriptionValidationEvent and expects a JSON echo of the validation code. “Unknown” usually means it didn’t get the response format it expects. A plain curl POST returning 204 is good, but it doesn’t exercise either of those two validation paths. Two quick checks to confirm what Azure is seeing: - Test the CloudEvents preflight:
Copy code
curl -i -X OPTIONS "<your-prefect-webhook-url>" -H "WebHook-Request-Origin: <http://eventgrid.azure.net|eventgrid.azure.net>"
Expected: 200 with a WebHook-Allowed-Origin header. If you get a 5xx here, that explains the CloudEvents failure. - Test the Event Grid schema validation POST:
Copy code
curl -i -X POST "<your-prefect-webhook-url>" \
  -H "Content-Type: application/json" \
  -d '[{
    "id":"1",
    "eventType":"Microsoft.EventGrid.SubscriptionValidationEvent",
    "subject":"test",
    "data":{"validationCode":"abc123","validationUrl":"<https://example>"},
    "eventTime":"2024-01-01T00:00:00Z",
    "metadataVersion":"1",
    "dataVersion":"1",
    "topic":"/subscriptions/.../resourceGroups/.../providers/Microsoft.EventGrid/topics/..."
  }]'
Expected: a 200 with a JSON body like {"validationResponse":"abc123"}. If you get a 204/empty or anything else, the Event Grid schema validation will fail. If either of those tests fails, that points to the webhook endpoint not handling Azure’s validation handshake as expected. We can dig in on our side if you share: - Webhook ID (the last path segment of the URL) - Approximate timestamps (with timezone) of the failing attempts - The Azure region of your Event Grid topic - Whether you’re using Event Grid system topic vs. custom topic, and which source service (e.g., Storage) Immediate workaround (reliable and commonly used): Put a tiny Azure Function/Logic App in front that: - Responds to CloudEvents handshake (OPTIONS) by echoing WebHook-Allowed-Origin - Responds to SubscriptionValidationEvent by echoing the validationResponse - For all other events, forwards the request to your Prefect webhook URL Example Azure Function (Python, minimal):
Copy code
import json
import os
import requests

PREFECT_WEBHOOK_URL = os.environ["PREFECT_WEBHOOK_URL"]

def main(req):
    # Handle CloudEvents webhook handshake (OPTIONS)
    if req.method == "OPTIONS":
        origin = req.headers.get("WebHook-Request-Origin", "*")
        return {
            "status_code": 200,
            "headers": {"WebHook-Allowed-Origin": origin},
            "body": ""
        }

    # Parse JSON body (Event Grid schema can be an array)
    try:
        body = req.get_json()
    except Exception:
        body = None

    # Handle Event Grid schema validation event (POST)
    if isinstance(body, list) and body and body[0].get("eventType") == "Microsoft.EventGrid.SubscriptionValidationEvent":
        code = body[0]["data"]["validationCode"]
        return {
            "status_code": 200,
            "headers": {"Content-Type": "application/json"},
            "body": json.dumps({"validationResponse": code})
        }

    # Forward everything else to Prefect
    headers = {k: v for k, v in req.headers.items() if k.lower() != "host"}
    resp = requests.request(req.method, PREFECT_WEBHOOK_URL, headers=headers, data=req.get_body())
    return {
        "status_code": resp.status_code,
        "headers": dict(resp.headers),
        "body": resp.text
    }
Then point Event Grid at the Function URL, and set PREFECT_WEBHOOK_URL to your Prefect webhook. This lets Azure complete validation and still delivers events to Prefect. Docs for context: - Prefect webhooks overview: Webhooks (Azure Event Grid notes)
- How to create a webhook: Create a webhook (Prefect Cloud) - Azure’s validation flows: Azure: Endpoint validation If you can share the webhook ID + timestamps, I’ll check logs and escalate if needed. In parallel, the Function/Logic App shim will unblock you immediately.