Flow
What my receiver does with each delivery now
1Look for the token
No Authorization header, or one that isn't a bearer token, gets turned away.
2Look for the client secret
Missing in production means refuse. Missing in development means any token passes.
3Check the signature
Only one signing method is accepted. A junk token or the wrong secret is refused.
4Read the body
Only callers that got past the token check reach the parser and the account lookup.
5Save the issue change
The event is written for the matching integration.
Issue tracker webhooks were being checked for a signature that nobody was ever going to send.
It expected each message to come with a hash of the body made from a shared secret. That works when both sides hold the secret. But my app connects to the tracker through OAuth, and when it signed up for events it never gave the tracker a secret. The tracker's docs say OAuth apps get something else: a signed token in the Authorization header.
So the receiver was waiting for a sender that didn't exist. Depending on settings, it either errored on every message, rejected every real message, or, outside production, let anyone in. I can't say which one real traffic hit, since I have no logs from before. But no setting checked a real message and accepted it. That's not a security control. It's a costume.
Checking the token the tracker actually sends
The receiver now asks for that token and checks its signature with the app's client secret. It only accepts one signing method, so the token can't pick an easier one. It checks before it parses the body, so strangers never reach the parser. With no secret set, production refuses everything. Development lets any token through, which is only safe if production is really marked as production.
The tests try no header, a bad header, a junk token, the wrong secret, a good token, and a missing secret in each environment, though the production one sends no header, so it never really reaches the secret check. The wrong-secret test is the important one. It's what proves the signature is really checked.
A setup step that only ever said "fine"
Signing up for events used to happen in the background, and nobody waited for it. If it failed, you got a log line, and the settings page showed a green ready box anyway.
Now the app waits for it. Success saves the subscription IDs. A failure is supposed to save the error. The page shows registered, failed, or unknown, and a new button asks the tracker what it really has.
Reading it back, the failure almost never gets saved. The helper catches its own error and returns an empty list:
catch (err) { log.error(err); return []; }
An empty list isn't an error, so the caller's error handling never runs. As far as I can tell, the page ends up saying unknown instead of failed. I moved the silence one function down and gave it a nicer panel.
The verify button is the part I trust, because it asks the tracker directly. If I wrote that helper again, I'd have it return either the IDs or a reason, and let the caller save whichever one is true.