Your application talks to other applications. That is how modern software works: payments, messaging, analytics, identity, all wired together with webhooks. Each webhook is a promise that when something happens, your system will hear about it and act.
The problem is that anyone can knock on that door.
In our latest batch of assessments, 22 percent of the companies we attacked had webhook or integration endpoints that could be forged, queried, or abused. This is the eighth finding in our field series, after endpoints that skip authentication, exposed internal tools, forgotten staging environments, secrets in JavaScript bundles, exposed origins, dead subdomains, and tokens in URLs. This one is about the messages your system trusts without checking.
What a webhook vulnerability is
A webhook vulnerability is an integration endpoint that accepts, reveals, or acts on data without verifying where it came from. MITRE tracks the core pattern as CWE-345, Insufficient Verification of Data Authenticity. The webhook is a public URL that performs real actions. If it does not verify the sender, the sender can be anyone.
A webhook is not a private line between two companies. It is a public endpoint that happens to be called by a partner. The trust has to be built into the endpoint, because the internet will not provide it.
What an attacker does with it
The attacker finds the endpoint, studies the format, and sends a message that looks right. Real findings from recent engagements, anonymized:
- An internal logs service exposed a webhook-log query API that accepted an arbitrary partner identifier with no authentication. Anyone could read another partner's delivery logs, and for a messaging platform, delivery logs contain message payloads.
- A scheduling platform let anyone mark a booking as a no-show, which fired the owner's no-show webhooks. The attacker was not just changing a record. They were pulling a trigger wired into other systems.
- A payments company's onboarding flow accepted a fabricated corporate identity, complete with a bogus registration number and dummy documents, and submitted it for review. The integration trusted the form, and the form trusted anyone.
- An email relay let an authenticated user send branded mail to any address. The relay was built to send notifications, and it sent whatever it was told.
The common thread: each system trusted the message because the message looked right, not because it could prove who sent it.
Worth saying: the core authentication at these companies usually held up under direct testing. The trust problem was at the integration boundary, not the login page.
Why this is a real problem
Webhooks are where trust is automated. When a webhook fires, your system does something: updates a record, sends money, sends email, changes a state. The action is real, and the trust behind it is a URL and a payload format.
If the endpoint does not verify the sender, the attacker does not need to break authentication. They need to know the format, and the format is usually documented, guessable, or visible in the public JavaScript bundle. The forged message then does exactly what a real message would do, and the audit trail records it as a normal event.
The blast radius is bigger than the endpoint. A forged webhook into one system often triggers actions in others: a no-show fires notifications, a payment confirmation releases an order, a status update triggers an email. One unverified message, many consequences, all of them looking legitimate.
Why teams leave webhooks unverified
The reason is that the integration worked in the test. The partner's message arrived, the system acted, the feature shipped. Verification was a step that could be added later, and later never came.
Some teams assume the endpoint is secret. It is not. Webhook URLs appear in documentation, in client code, in logs, in support tickets. An unguessable URL is not authentication; it is a URL.
Some teams assume the partner is the only one who knows the format. Formats leak. The payload shape is in the partner's documentation, in your own error messages, in the JavaScript that renders the result. Reverse-engineering a webhook format is an afternoon, not a research project.
And some teams rely on the network: the request came from a partner IP, so it must be real. IP addresses can be spoofed at the routing level less often than people fear, but they can also be shared, proxied, or simply wrong after the partner migrates. An IP allowlist is a speed bump, not a signature.
How to secure webhooks
The fix is to make the message prove itself. Four steps.
Step one. Sign every webhook. The sender signs the payload with a shared secret, and your endpoint verifies the signature before doing anything. Every major provider supports this. If a partner cannot sign, treat their webhook as untrusted input, not as a command.
Step two. Verify the event, not just the signature. Check that the event type is one you handle, the identifiers belong to you, and the payload matches the schema. A valid signature on an unexpected event is still an unexpected event.
Step three. Replay protection. Include a timestamp and a nonce in the payload, and reject old or repeated messages. A captured webhook should not be replayable an hour later, or a hundred times.
Step four. Test the endpoint like an attacker. Send forged messages, unsigned messages, replayed messages, and messages for other tenants. Expect rejection. Then re-test after every integration change, because webhooks multiply with every new vendor. This is how we work: the check returns when the software changes.
Frequently asked questions
What is a webhook?
A webhook is an HTTP callback: one system sends a message to a URL you provide when an event happens, and your system acts on it. Payment confirmations, status updates, and notification triggers are common examples. The webhook URL is public, so it must verify its callers.
How do I verify a webhook is real?
Check the signature. Most providers sign webhook payloads with a shared secret, and you verify that signature before acting. Then check the event type and identifiers match what you expect, and reject old or repeated messages with timestamps and nonces.
What happens if I don't verify webhooks?
Anyone who learns the URL and the payload format can send forged events, and your system will act on them as if they were real. The actions are real. The audit trail looks normal. And the blast radius often extends into other systems the webhook triggers.
Is a secret webhook URL enough protection?
No. URLs are not secrets. They appear in documentation, client code, logs, and support tickets. An unguessable URL is a speed bump, not authentication. The endpoint must verify the sender, not just the address.
How do I test my webhook security?
Send the endpoint forged messages, unsigned messages, replayed messages, and messages for other tenants, and expect rejection in every case. Automate these checks and re-run them after every integration change, because webhooks multiply with every new vendor.
The point
We keep finding unverified webhooks because the integration worked in the test, and verification was a step for later. Later never came, and the door stayed open.
If you want to know where you stand, it takes a few minutes. Pick your most sensitive webhook endpoint and send it a message with no signature. If it acts, that is the finding.
And if you would rather have someone forge the messages, replay them, and check every integration you add, that is exactly what we do.
Related reading: The Finding We Report More Than Any Other — the endpoint-level version of the same trust problem. And Everything Your JavaScript Bundle Tells an Attacker — where attackers often learn your webhook format.