Ship It Conversations: Jay Lark of Hookbridge on Webhook Reliability, Retries, Idempotency, Replay, HMAC Security, Local Testing, and Operating Webhooks in Production
Try reloading the player or open this episode directly on YouTube.
In this episode, Jay Lark of Hookbridge discusses the complexities of webhooks, including reliability, retries, and security. He explains key concepts like idempotency, event IDs, and the importance of observability in production environments.
Now Playing
Ship It Conversations: Jay Lark of Hookbridge on Webhook Reliability, Retries, Idempotency, Replay, HMAC Security, Local Testing, and Operating Webhooks in Production
Ship It Weekly
0:0031:26
Chapters
Jump to a section in this episode.
Speed & share
Transcript
Webhooks might be the simplest distributed system
most teams underestimate. You give a service
a URL. It sends an HTTP POST when something happens.
Done. Until your endpoint is down during a deploy.
Or the sender retries and you process the same
event twice. Or events arrive out of order. Or
a valid request triggers something it should
not. Or you discover the provider stopped retrying
hours ago and nobody noticed. Suddenly, that
simple HTTP POST needs queues, idempotency,
signature verification, replay, observability,
retention, and someone willing to own it when
it breaks. That is the strange thing about webhooks.
The interface is simple. The operational contract
is not. I'm Brian Teller from Teller's Tech.
And this is Ship It Weekly. Welcome back to Ship
It Weekly, where I filter the noise and focus
on what matters when you are the one running
infrastructure and owning reliability. Most weeks,
it's a quick DevOps, SRE, platform, cloud, and
security news recap. In between those, I do conversation
episodes with people building and operating the
systems we all depend on. Today I'm joined by
Jay Lark, Principal DevOps Engineer and the founder
The thing that stuck with me most from this conversation is how much infrastructure can hide behind one tiny interface.
A webhook looks almost too simple to be dangerous.
Something happens.
One service sends an HTTP POST to another service.
The receiver returns a 200.
Done.
That simplicity is why webhooks became so common. They are easy to explain, easy to prototype, and easy to add to an integration without introducing a full message broker, event bus, or another major piece of infrastructure.
But the simple interface hides a much more complicated contract.
What happens if the receiving endpoint is down?
What happens if it is slow?
What happens if the sender times out, retries, and delivers the same event twice?
What happens if the events arrive in the wrong order?
What happens if the receiver returns a 200, but something fails internally before the event is actually processed?
What happens if the event was valid when it was created, but it arrives late enough that acting on it would now put the system into the wrong state?
Those are not really HTTP questions anymore.
Those are distributed systems questions.
And that is where I think this conversation with Jay lands in a useful place for DevOps, SRE, and platform teams.
The webhook itself is rarely the hard part.
The hard part is everything you eventually have to build around it.
Queues.
Retries.
Backoff.
Deduplication.
Ordering.
Idempotency.
Signature verification.
Retention.
Replay.
Observability.
Local testing.
Secret rotation.
Deployment availability.
Somewhere for failed events to go.
And eventually, someone on call who has to understand why an event disappeared, arrived twice, or showed up several minutes after the system had already moved on.
That is a lot of machinery for something that began as a curl command.
The “at least once delivery” discussion is probably the clearest example.
That wording sounds reassuring.
At least once.
The event will get there.
But what it really tells the receiver is that duplicates are part of the contract.
The sender might deliver the event once.
It might deliver it twice.
It might deliver it ten times.
If the event is only updating a dashboard counter, maybe that is annoying.
If the event triggers a charge, refund, account change, infrastructure deployment, license creation, or customer provisioning workflow, processing it ten times is a very different problem.
That is why idempotency cannot be treated like an optional optimization.
It is how the receiver safely handles the delivery guarantee it was given.
The application needs some way to identify the event, know whether it has already been processed, and avoid repeating an action that should only happen once.
And that sounds straightforward until event ordering enters the picture.
Maybe the “order updated” event arrives before “order created.”
Maybe a retry for an older account state shows up after a newer event was already processed.
Maybe a customer changes their billing information, cancels the account, and then a delayed retry for that earlier billing change arrives.
The event itself may be authentic.
It may be correctly signed.
It may not be a duplicate.
And it may still be wrong to apply.
That is an important distinction.
Delivery correctness is not the same as business correctness.
A system can successfully receive and authenticate an event that should no longer change the current state.
So the receiver needs more than an event ID.
It may need timestamps, versions, sequence numbers, state checks, or some understanding of whether that transition is still valid.
That gets us into the receiver burden, which was probably the part of Jay’s argument I agreed with most.
Webhooks move a lot of responsibility to the consumer.
The sender gets to say, “Here is the event.”
The receiver has to expose an endpoint.
Keep it available.
Respond quickly enough.
Verify who sent the request.
Protect against replay.
Handle duplicates.
Deal with events arriving out of order.
Avoid losing the event after acknowledgment.
Protect sensitive payloads.
Store enough information to debug failures.
And somehow make all of that survive deployments, infrastructure problems, certificate issues, dependency failures, and whatever else is happening at the exact moment the event arrives.
The sender controls when the event is delivered.
The receiver has to be ready whenever that happens.
That is a pretty uneven reliability contract.
And it gets worse when providers have different behavior.
Some retry automatically.
Some retry for a limited period.
Some make you initiate a redelivery manually.
Some expose useful delivery logs.
Some give you an HTTP status and not much else.
Some provide unique event IDs and timestamps.
Some give you a payload and wish you luck.
The receiving team has to understand the behavior of every provider it depends on, because the word “webhook” does not mean every integration has the same delivery guarantees.
That is one reason a 200 response can become misleading.
A 200 usually means the receiver accepted the request.
It does not necessarily mean the event completed its real job.
Maybe the endpoint accepted the payload and placed it into a queue.
Maybe it tried to place it into a queue and failed after responding.
Maybe the request handler wrote to a database, but the downstream worker failed.
Maybe the event triggered a workflow that broke three services later.
Maybe the response went back successfully, but the internal transaction never committed.
From the sender’s perspective, delivery worked.
From the business’s perspective, nothing happened.
That gap is where replay becomes important.
Replay feels like an advanced feature until the first time a system acknowledges an event and then fails internally.
At that point, replay becomes the difference between recovery and manual data repair.
Can you find the event?
Can you understand what happened?
Can you safely run it again?
Can you avoid repeating the parts that already succeeded?
Can you replay a group of events in the correct order?
Can you do it without asking a provider’s support team to resend something from three days ago?
Those are incident-response questions.
And if the answer is “we did not retain enough information,” then the incident becomes much harder than it needed to be.
The observability side of this is tricky too.
You need enough information to reconstruct the delivery.
When did the sender connect?
What URL did it call?
How long did the request take?
What headers were present?
Did signature verification pass?
What status did the receiver return?
Was the event queued?
Was it processed?
Was it retried?
Did it eventually succeed?
But “log everything” is not a safe answer.
Webhook payloads can contain customer details, billing data, email addresses, internal identifiers, tokens, or whatever else the sending application decided to include.
Headers may contain signatures or credentials.
The information that makes debugging easier can also become a security and privacy problem if it is stored carelessly.
So webhook observability needs the same discipline as every other production telemetry system.
Collect what you need.
Redact what you do not.
Limit who can access it.
Set retention intentionally.
Do not dump entire payloads into logs just because debugging was painful once.
And make sure the data required for replay is treated differently from the data used for routine logging.
Those may overlap, but they are not automatically the same thing.
The security discussion had another distinction that I think matters.
A valid HMAC signature proves something useful.
It can show that the message came from someone holding the expected secret and that the payload was not modified after it was signed.
That matters.
You should verify it.
But a valid signature does not mean the requested action is safe.
It authenticates the message.
It does not approve the business logic.
If a signed payload says to refund an order, your application still needs to verify that the order exists, belongs to the right customer, is in a refundable state, and has not already been refunded.
If the payload references a project, account, organization, or tenant, the receiver still needs to enforce those boundaries.
Trusted sender does not mean trusted outcome.
That is the same mistake teams make in other systems when authentication and authorization get collapsed into one decision.
The request came from the right place.
Good.
Now decide whether it should be allowed to do what it is asking.
Replay attacks fit into that same model.
An attacker may not need to forge a valid event if they can capture and resend a legitimate one.
That is why timestamps, processing windows, unique event IDs, and deduplication matter alongside signature verification.
The signature can still be valid.
The event can still be authentic.
It may just be old, already processed, or no longer appropriate.
Again, the hard part is not whether the HTTP request is technically valid.
The hard part is whether acting on it is valid now.
The local development discussion also hit something nearly every developer who has built a webhook integration has dealt with.
Your application is running on localhost.
The provider is on the public internet.
The provider cannot send an event directly to your laptop.
So now you need a tunnel, a staging deployment, a copied payload, or a mocked event.
Each option adds friction.
Temporary tunnel URLs change, so the callback configuration has to be updated.
Staging deployments turn a one-line code change into a full CI/CD cycle.
Copied payloads lose some of the real headers and signing behavior.
Mocks are useful, but they tend to model the payload you expect rather than the ugly one the provider eventually sends.
The value of forwarding a real event to local code is not just convenience.
It lets the developer see the actual request, hit a breakpoint, inspect the headers, verify signatures, and debug the integration without turning every change into a cloud deployment.
That is a developer experience problem, but it becomes a reliability problem when the bad local workflow encourages teams to test less.
When feedback is slow, people take shortcuts.
They test the happy path.
They skip the signature check until later.
They assume the payload shape will stay consistent.
They deploy to staging and hope they can reproduce the issue.
Better local tooling shortens that loop and makes it easier to test the parts that usually break.
I also liked Jay’s argument that polling is underrated.
Webhooks became the modern default partly because polling can be wasteful.
Checking every few seconds for an event that almost never happens is inefficient for both sides.
Webhooks fix that by pushing the event when it happens.
But that does not mean polling became universally wrong.
Polling gives the consumer control over when it receives work.
It can make ordering easier.
It can simplify recovery.
It avoids exposing another inbound endpoint.
It may be a better fit for a laptop, home automation system, private network, or anything else that should not accept unsolicited connections from the public internet.
The right question is not “are webhooks better than polling?”
The right question is “which delivery model creates the failure modes we are better prepared to operate?”
Sometimes push is clearly better.
Sometimes pull is simpler and safer.
Sometimes the best design is a service receiving the webhook, retaining it, and letting the internal consumer pull events when it is ready.
That is not going backward.
That is choosing the contract that matches the system.
The build-versus-buy question lands in the same place.
Most teams can write the first version of webhook delivery themselves.
That is not the issue.
The first version is an HTTP client or a route handler.
The expensive part is the second, third, and tenth version.
The version with exponential backoff.
The version with jitter.
The version with delivery logs.
The version with replay.
The version with configurable retention.
The version with secret rotation.
The version that signs outgoing events.
The version that handles thousands of endpoints.
The version that lets customers understand why their endpoint keeps failing.
The version that does not create a retry storm when a large customer goes down.
The version that somebody has to maintain for the next five years.
That does not automatically mean every team should buy a platform.
There are cases where the webhook behavior is small, internal, low volume, and easy to own.
But the decision should be based on the full lifecycle, not the first implementation.
Do we want to own this capability?
Do we understand the delivery guarantees?
Who supports it?
Who handles incidents?
How long do we retain events?
How do customers replay them?
How do secrets rotate?
How do we prevent one failing endpoint from consuming all the workers?
How do we test the full path?
If the answer is that this is becoming its own product inside your product, then it may be time to stop pretending it is only an HTTP POST.
That is really my takeaway from the episode.
Webhooks are not bad.
They are useful because they hide complexity from the integration boundary.
But that complexity does not disappear.
It moves into the operating model.
So start with the failure path.
Assume duplicates.
Assume delays.
Assume events will arrive out of order.
Assume the receiver will eventually be unavailable.
Assume internal processing can fail after acknowledgment.
Verify signatures, but still verify the action.
Keep enough information to debug and replay events, without turning logs into a collection of sensitive payloads.
Test deployments while events are arriving.
Know what the provider does when delivery fails.
And decide whether push is really the right model before exposing another endpoint to the internet.
This is a guest conversation episode of Ship It Weekly, separate from the weekly news recaps.
In this Ship It Conversations episode, I talk with Jay Lark of Hookbridge about webhook reliability, retries, idempotency, replay, security, local development, and what happens when a simple HTTP POST becomes production infrastructure.
Jay is a Principal DevOps Engineer and the founder of Hookbridge, a service focused on making webhook delivery more reliable and easier to operate.
We start with the basic model: one service sends an HTTP POST to another when something happens. The happy path is easy. The problems begin when an endpoint is unavailable, responds too slowly, receives duplicate events, or gets them out of order.
Jay explains what “at least once delivery” means and why receivers must expect duplicates. We talk about idempotency, event IDs, retry behavior, availability during deployments, and why returning a 200 does not prove downstream processing succeeded.
We also dig into observability and security. Teams need enough visibility to know whether a webhook arrived, whether signature verification passed, what response was returned, and where processing failed. Jay breaks down HMAC signatures, timestamps, replay protection, and why a valid signature still does not replace normal business-logic validation.
Local development is another source of friction. External providers cannot send events directly to localhost, so developers often rely on temporary tunnels, staging deployments, copied payloads, or mocks. Jay explains how Hookbridge uses a fixed URL and local client to forward real webhook traffic to a developer’s machine.
We also talk about n8n, self-hosted OpenClaw systems, Hookbridge pull endpoints, and when polling may be simpler or safer than exposing another inbound endpoint.
The big takeaway: design the failure path before a webhook becomes business-critical. Verify the sender, expect duplicate and out-of-order events, build enough visibility to debug failures, and have a replay strategy before the first incident.
Highlights
• Why webhooks are harder than “just an HTTP POST”
• What at-least-once delivery means for receivers
• Why idempotency, retries, and event ordering matter
• What teams need for webhook observability and debugging
• Where HMAC signatures, timestamps, and replay protection fit
• Why local webhook development is still awkward
• When polling or pull-based delivery may be a better fit
• When teams should stop building webhook infrastructure themselves
The thing that stuck with me most from this conversation is how much infrastructure can hide behind one tiny interface.
A webhook looks almost too simple to be dangerous.
Something happens.
One service sends an HTTP POST to another service.
The receiver returns a 200.
Done.
That simplicity is why webhooks became so common. They are easy to explain, easy to prototype, and easy to add to an integration without introducing a full message broker, event bus, or another major piece of infrastructure.
But the simple interface hides a much more complicated contract.
What happens if the receiving endpoint is down?
What happens if it is slow?
What happens if the sender times out, retries, and delivers the same event twice?
What happens if the events arrive in the wrong order?
What happens if the receiver returns a 200, but something fails internally before the event is actually processed?
What happens if the event was valid when it was created, but it arrives late enough that acting on it would now put the system into the wrong state?
Those are not really HTTP questions anymore.
Those are distributed systems questions.
And that is where I think this conversation with Jay lands in a useful place for DevOps, SRE, and platform teams.
The webhook itself is rarely the hard part.
The hard part is everything you eventually have to build around it.
Queues.
Retries.
Backoff.
Deduplication.
Ordering.
Idempotency.
Signature verification.
Retention.
Replay.
Observability.
Local testing.
Secret rotation.
Deployment availability.
Somewhere for failed events to go.
And eventually, someone on call who has to understand why an event disappeared, arrived twice, or showed up several minutes after the system had already moved on.
That is a lot of machinery for something that began as a curl command.
The “at least once delivery” discussion is probably the clearest example.
That wording sounds reassuring.
At least once.
The event will get there.
But what it really tells the receiver is that duplicates are part of the contract.
The sender might deliver the event once.
It might deliver it twice.
It might deliver it ten times.
If the event is only updating a dashboard counter, maybe that is annoying.
If the event triggers a charge, refund, account change, infrastructure deployment, license creation, or customer provisioning workflow, processing it ten times is a very different problem.
That is why idempotency cannot be treated like an optional optimization.
It is how the receiver safely handles the delivery guarantee it was given.
The application needs some way to identify the event, know whether it has already been processed, and avoid repeating an action that should only happen once.
And that sounds straightforward until event ordering enters the picture.
Maybe the “order updated” event arrives before “order created.”
Maybe a retry for an older account state shows up after a newer event was already processed.
Maybe a customer changes their billing information, cancels the account, and then a delayed retry for that earlier billing change arrives.
The event itself may be authentic.
It may be correctly signed.
It may not be a duplicate.
And it may still be wrong to apply.
That is an important distinction.
Delivery correctness is not the same as business correctness.
A system can successfully receive and authenticate an event that should no longer change the current state.
So the receiver needs more than an event ID.
It may need timestamps, versions, sequence numbers, state checks, or some understanding of whether that transition is still valid.
That gets us into the receiver burden, which was probably the part of Jay’s argument I agreed with most.
Webhooks move a lot of responsibility to the consumer.
The sender gets to say, “Here is the event.”
The receiver has to expose an endpoint.
Keep it available.
Respond quickly enough.
Verify who sent the request.
Protect against replay.
Handle duplicates.
Deal with events arriving out of order.
Avoid losing the event after acknowledgment.
Protect sensitive payloads.
Store enough information to debug failures.
And somehow make all of that survive deployments, infrastructure problems, certificate issues, dependency failures, and whatever else is happening at the exact moment the event arrives.
The sender controls when the event is delivered.
The receiver has to be ready whenever that happens.
That is a pretty uneven reliability contract.
And it gets worse when providers have different behavior.
Some retry automatically.
Some retry for a limited period.
Some make you initiate a redelivery manually.
Some expose useful delivery logs.
Some give you an HTTP status and not much else.
Some provide unique event IDs and timestamps.
Some give you a payload and wish you luck.
The receiving team has to understand the behavior of every provider it depends on, because the word “webhook” does not mean every integration has the same delivery guarantees.
That is one reason a 200 response can become misleading.
A 200 usually means the receiver accepted the request.
It does not necessarily mean the event completed its real job.
Maybe the endpoint accepted the payload and placed it into a queue.
Maybe it tried to place it into a queue and failed after responding.
Maybe the request handler wrote to a database, but the downstream worker failed.
Maybe the event triggered a workflow that broke three services later.
Maybe the response went back successfully, but the internal transaction never committed.
From the sender’s perspective, delivery worked.
From the business’s perspective, nothing happened.
That gap is where replay becomes important.
Replay feels like an advanced feature until the first time a system acknowledges an event and then fails internally.
At that point, replay becomes the difference between recovery and manual data repair.
Can you find the event?
Can you understand what happened?
Can you safely run it again?
Can you avoid repeating the parts that already succeeded?
Can you replay a group of events in the correct order?
Can you do it without asking a provider’s support team to resend something from three days ago?
Those are incident-response questions.
And if the answer is “we did not retain enough information,” then the incident becomes much harder than it needed to be.
The observability side of this is tricky too.
You need enough information to reconstruct the delivery.
When did the sender connect?
What URL did it call?
How long did the request take?
What headers were present?
Did signature verification pass?
What status did the receiver return?
Was the event queued?
Was it processed?
Was it retried?
Did it eventually succeed?
But “log everything” is not a safe answer.
Webhook payloads can contain customer details, billing data, email addresses, internal identifiers, tokens, or whatever else the sending application decided to include.
Headers may contain signatures or credentials.
The information that makes debugging easier can also become a security and privacy problem if it is stored carelessly.
So webhook observability needs the same discipline as every other production telemetry system.
Collect what you need.
Redact what you do not.
Limit who can access it.
Set retention intentionally.
Do not dump entire payloads into logs just because debugging was painful once.
And make sure the data required for replay is treated differently from the data used for routine logging.
Those may overlap, but they are not automatically the same thing.
The security discussion had another distinction that I think matters.
A valid HMAC signature proves something useful.
It can show that the message came from someone holding the expected secret and that the payload was not modified after it was signed.
That matters.
You should verify it.
But a valid signature does not mean the requested action is safe.
It authenticates the message.
It does not approve the business logic.
If a signed payload says to refund an order, your application still needs to verify that the order exists, belongs to the right customer, is in a refundable state, and has not already been refunded.
If the payload references a project, account, organization, or tenant, the receiver still needs to enforce those boundaries.
Trusted sender does not mean trusted outcome.
That is the same mistake teams make in other systems when authentication and authorization get collapsed into one decision.
The request came from the right place.
Good.
Now decide whether it should be allowed to do what it is asking.
Replay attacks fit into that same model.
An attacker may not need to forge a valid event if they can capture and resend a legitimate one.
That is why timestamps, processing windows, unique event IDs, and deduplication matter alongside signature verification.
The signature can still be valid.
The event can still be authentic.
It may just be old, already processed, or no longer appropriate.
Again, the hard part is not whether the HTTP request is technically valid.
The hard part is whether acting on it is valid now.
The local development discussion also hit something nearly every developer who has built a webhook integration has dealt with.
Your application is running on localhost.
The provider is on the public internet.
The provider cannot send an event directly to your laptop.
So now you need a tunnel, a staging deployment, a copied payload, or a mocked event.
Each option adds friction.
Temporary tunnel URLs change, so the callback configuration has to be updated.
Staging deployments turn a one-line code change into a full CI/CD cycle.
Copied payloads lose some of the real headers and signing behavior.
Mocks are useful, but they tend to model the payload you expect rather than the ugly one the provider eventually sends.
The value of forwarding a real event to local code is not just convenience.
It lets the developer see the actual request, hit a breakpoint, inspect the headers, verify signatures, and debug the integration without turning every change into a cloud deployment.
That is a developer experience problem, but it becomes a reliability problem when the bad local workflow encourages teams to test less.
When feedback is slow, people take shortcuts.
They test the happy path.
They skip the signature check until later.
They assume the payload shape will stay consistent.
They deploy to staging and hope they can reproduce the issue.
Better local tooling shortens that loop and makes it easier to test the parts that usually break.
I also liked Jay’s argument that polling is underrated.
Webhooks became the modern default partly because polling can be wasteful.
Checking every few seconds for an event that almost never happens is inefficient for both sides.
Webhooks fix that by pushing the event when it happens.
But that does not mean polling became universally wrong.
Polling gives the consumer control over when it receives work.
It can make ordering easier.
It can simplify recovery.
It avoids exposing another inbound endpoint.
It may be a better fit for a laptop, home automation system, private network, or anything else that should not accept unsolicited connections from the public internet.
The right question is not “are webhooks better than polling?”
The right question is “which delivery model creates the failure modes we are better prepared to operate?”
Sometimes push is clearly better.
Sometimes pull is simpler and safer.
Sometimes the best design is a service receiving the webhook, retaining it, and letting the internal consumer pull events when it is ready.
That is not going backward.
That is choosing the contract that matches the system.
The build-versus-buy question lands in the same place.
Most teams can write the first version of webhook delivery themselves.
That is not the issue.
The first version is an HTTP client or a route handler.
The expensive part is the second, third, and tenth version.
The version with exponential backoff.
The version with jitter.
The version with delivery logs.
The version with replay.
The version with configurable retention.
The version with secret rotation.
The version that signs outgoing events.
The version that handles thousands of endpoints.
The version that lets customers understand why their endpoint keeps failing.
The version that does not create a retry storm when a large customer goes down.
The version that somebody has to maintain for the next five years.
That does not automatically mean every team should buy a platform.
There are cases where the webhook behavior is small, internal, low volume, and easy to own.
But the decision should be based on the full lifecycle, not the first implementation.
Do we want to own this capability?
Do we understand the delivery guarantees?
Who supports it?
Who handles incidents?
How long do we retain events?
How do customers replay them?
How do secrets rotate?
How do we prevent one failing endpoint from consuming all the workers?
How do we test the full path?
If the answer is that this is becoming its own product inside your product, then it may be time to stop pretending it is only an HTTP POST.
That is really my takeaway from the episode.
Webhooks are not bad.
They are useful because they hide complexity from the integration boundary.
But that complexity does not disappear.
It moves into the operating model.
So start with the failure path.
Assume duplicates.
Assume delays.
Assume events will arrive out of order.
Assume the receiver will eventually be unavailable.
Assume internal processing can fail after acknowledgment.
Verify signatures, but still verify the action.
Keep enough information to debug and replay events, without turning logs into a collection of sensitive payloads.
Test deployments while events are arriving.
Know what the provider does when delivery fails.
And decide whether push is really the right model before exposing another endpoint to the internet.
The happy path is a curl command.
Production is everything around it.
Build that part deliberately.
Then ship the webhook.
Additional Links
Hookbridge: https://hookbridge.io
Hookbridge local development CLI: https://www.hookbridge.io/cli.html
Hookbridge pull endpoints: https://www.hookbridge.io/pull.html
Jay Lark on LinkedIn: https://www.linkedin.com/in/jay-lark-ba7a3b5/
Stripe webhook documentation: https://docs.stripe.com/webhooks
GitHub webhook documentation: https://docs.github.com/en/webhooks
n8n Webhook node: https://docs.n8n.io/integrations/builtin/core-nodes/n8n-nodes-base.webhook/
OpenClaw: https://openclaw.ai