The loop
Create an endpoint and it prints a permanent ingest URL. Everything sent there is captured whole — headers, body, the exact bytes — before anything else happens. Stream those events to a port on your machine, and replay any one of them as often as you like while you fix the handler. Forwarding runs from your own machine, so it isn't a billable delivery.
What's different about Microsoft Teams
Two things about Teams Outgoing Webhooks catch developers out. First, there's no event catalogue and no subscription screen: the only documented trigger is a person @mentioning the webhook by name, and only in a public channel of the team it was created in — never in personal or private scope. Second, delivery is synchronous and reply-shaped. Teams issues an HTTP request, your code gets five seconds to respond before the connection times out, and whatever you return is posted back into the same reply chain. A handler that acknowledges and defers has nothing to say in the thread.
Where the URL goes
In the Teams client, select Teams in the left pane, pick the team, hit ••• and choose Manage team, open the Apps tab, and under Upload an app select Create an outgoing webhook. Fill in Name, Callback URL — the HTTPS endpoint that accepts JSON payloads and receives POST requests from Teams — and Description. On Create, an HMAC dialogue appears with a security token that "doesn't expire and is unique for each configuration". Copy it then.
Neither mechanism documents a test-send facility, so the first real action is the test. For an Outgoing Webhook, post a message in a public channel of that team @mentioning the webhook by name. For Microsoft Graph change notifications, the subscription request itself makes Graph POST a validationToken query to your notification URL, so the URL sees traffic before any Teams message exists.
What Microsoft Teams sends
Outgoing Webhooks have no event vocabulary at all — nothing to select, nothing to subscribe to. The identifiers below belong to Microsoft Graph change notifications: lowercase past-tense changeType verbs, comma-joined on a subscription, plus lifecycleEvent values delivered to a separate lifecycleNotificationUrl. Graph documents deleted only for chat-message subscriptions.
- created
- updated
- deleted
- reauthorizationRequired
- subscriptionRemoved
Worth knowing first
Five seconds, and it's a reply
The how-to page gives your code five seconds to respond before the connection times out and terminates. The older webhooks-and-connectors overview says ten for the same behaviour, so the official docs disagree with themselves — build against five. Your response body appears in the same reply chain as the original message, which makes it user-visible copy, not an internal acknowledgement.
Validate the HMAC on every request
Each request carries an authorization header of the form HMAC followed by a base64 value. Compute a standard SHA256 HMAC over the UTF-8 byte array of the raw body, keyed with the base64-decoded security token from registration, and compare. The docs say your code must always validate it. Teams only makes the webhook available if the URL is valid and the server and client tokens are equal.
Scoped to one team, public channels only
Outgoing Webhooks are configured per team and visible to all that team's members, and they cannot be included as part of a normal Teams app. Users can only message one in public channels, not in personal or private scope. A webhook registered in one team does not exist anywhere else, so exercise it where you created it.
Graph validates the URL before subscribing
Creating a Microsoft Graph change-notification subscription first POSTs to your notification URL with a validationToken query parameter. You have ten seconds to reply with 200 OK, content type text/plain, and the URL-decoded token as plain text in the body. Return it still encoded and validation fails; fail validation and Graph doesn't create the subscription. The endpoint must be publicly accessible over HTTPS.
Graph marks slow endpoints, then drops
Graph counts a notification delivered only on a 2xx received within three seconds, then retries at exponential-backoff intervals for up to four hours with the per-attempt timeout extended to ten seconds. Blow the three-second allowance on more than 10% of responses in a ten-minute window and the endpoint is marked slow; blow the ten-second one on more than 15% and notifications are dropped. Microsoft suggests queueing and returning 202 Accepted.
Outgoing Webhook retries are undocumented
The how-to page describes the synchronous request and response and nothing else: no retry policy, no schedule, no success status code, no payload size cap, no ordering statement. Don't carry the Graph numbers across — they belong to a different product. Until Microsoft documents otherwise, treat a timed-out delivery as a message you will not see again.
Signature checking is handled for you once the secret is registered — you can also check a captured signature by hand in the signature verifier.