A Solana webhook is a hosted watcher: you register a set of addresses with a provider, expose an HTTPS endpoint, and the provider posts matching transactions to it. You stop running a socket and start running a web service. The guarantees change from ones you control to ones you inherit, and almost all of them are at-least-once delivery with no ordering promise.

That single sentence sets the entire design. A receiver that assumes each delivery is unique and sequential will double count under load and misorder events during retries. A receiver built for duplicates and disorder is straightforward, and it is the difference between a pipeline that survives a provider incident and one that quietly corrupts a week of totals.

What a webhook is in this context

Several Solana infrastructure providers offer address-watching products. Helius publishes webhook types in raw and enhanced variants, and QuickNode, Chainstack, Shyft, Syndica, Alchemy and Triton One each publish some combination of RPC, streaming or push delivery with their own semantics. This desk names them because naming them is the only accurate way to describe the landscape; it does not rank them and does not quote their commercial terms.

Mechanically the shape is the same everywhere. You supply a list of accounts to watch, optionally a filter on the kind of activity you care about, a destination URL, and an authentication secret. The provider runs the ingestion, matches transactions against your list, and posts them to you. Whether the payload is a raw transaction or a decoded interpretation is a product choice on their side.

The important framing is that a webhook is not a different view of the chain. It is the same blocks, read by somebody else's pipeline, at a commitment level they chose, decoded by rules they wrote. Everything you cannot see about that pipeline becomes an assumption in yours.

The guarantees you inherit

PropertyWhat is normally trueWhat it forces on your design
DeliveryAt-least-once, with retries on non-success responsesEvery write must be idempotent, keyed on something stable
OrderingNot promised; retries and parallel workers reorderSort by slot and position from the payload, never by arrival
TimeoutA few seconds before the attempt counts as failedAcknowledge before you process; queue the work
Retry budgetFinite, then the delivery is abandonedYou need a backfill path for whatever falls off the end
Commitment levelChosen by the provider, documented with varying precisionRead their reference; do not assume it matches your other feeds
Payload shapeRaw transaction, or the provider's decoded formDecoded payloads embed decisions you did not make

Only two of those rows are about speed, and neither is the reason to choose or reject the transport. The reason to choose a webhook is that you would rather run a web service than a socket consumer. The reason to reject one is that you need control over exactly the properties in this table.

Configuring a watch

Three configuration choices carry most of the consequences. The first is the address list. Watching a program id gives you everything that program touches, which is broad and stable. Watching individual pool or vault accounts is narrower and cheaper to process, and it goes stale the moment the active pool for a pair changes, leaving a filter that is technically correct and matches nothing.

The second is the activity filter. Providers commonly let you restrict deliveries to a category such as swaps or transfers. This is convenient and it is a place where the provider's classification silently becomes yours: a venue they do not classify as a swap venue produces nothing, and the absence looks exactly like inactivity rather than like an unmatched filter.

The third is raw against decoded payloads. Raw gives you the transaction and its metadata, and leaves the interpretation to you, which is more work and fully auditable. Decoded gives you a tidy object with amounts already attributed, which is faster to build on and impossible to check without fetching the raw transaction anyway. Most teams start decoded and move at least their important paths to raw once a figure gets questioned.

Designing the receiver

The receiver is a small piece of software with unusually strict requirements. It has to be always available, fast to answer, safe to run twice, and unbothered by payloads arriving in the wrong order. The sequence below satisfies all four, and it is deliberately boring.

  1. Verify before parsing. Check the shared secret or signature header with a constant-time comparison and reject anything that fails, before the body is deserialised.
  2. Persist the raw body. Write the untouched payload to a durable queue or append-only store, with the received timestamp and a generated delivery id.
  3. Answer immediately. Return success as soon as the payload is durable. Every millisecond spent decoding inside the handler is a millisecond closer to a retry you did not need.
  4. Process asynchronously. A worker reads the queue, decodes the transaction, and produces countable events. Failures here retry on your schedule, not the provider's.
  5. Write idempotently. Insert events keyed by transaction signature plus instruction index so that a repeated delivery updates nothing.
  6. Advance the watermark. Record the highest slot fully processed, which is what makes a later gap check possible at all.

Step three is the one that gets argued about, and the argument is always resolved the same way after the first incident. If you decode inside the request handler, a slow decode causes a timeout, the timeout causes a retry, the retry arrives while the first attempt is still running, and now you have concurrency problems on top of a latency problem.

Idempotency, with the arithmetic

The following numbers are illustrative and exist only to show how quickly duplicates distort a total. Suppose a provider delivers 10,000 swap events during an hour, and a brief incident on your side causes 4 percent of them to be retried after your handler had already committed the work.

Receiver designRows writtenIllustrative effect on the hourly total
Blind insert on every delivery10,400Reported activity is 4 percent higher than reality
Insert with a unique key on signature onlyUnder-countedMulti-fill transactions collapse into one row and the total drops
Insert with a unique key on signature plus instruction index10,000Correct, and correct again on the next retry

The middle row is the interesting failure, because it is the fix people reach for first. Keying on the signature alone deduplicates deliveries and also deduplicates legitimate separate fills inside one transaction, which is a different error in the opposite direction. The key has to be as unique as the thing you are counting, no more and no less.

The ordering you do not get

Arrival order tells you about your network and the provider's worker pool. It tells you nothing about the chain. Two transactions from the same slot can arrive seconds apart, and a retried delivery from ten minutes ago can land between two fresh ones.

Order has to be reconstructed from the payload. The slot number gives you the coarse sequence, and within a slot the position of the transaction in the block gives you the fine sequence. Both are properties of the ledger and both are stable across redeliveries, which is exactly what arrival time is not.

This matters most for anything stateful. A running position, a rolling window that closes, or an alert that depends on a sequence of events will all misbehave if they are driven by arrival. Compute state from ordered data, not from the order the network happened to hand you.

The availability coupling nobody plans for

With a socket, an outage on your side is your problem and the fix is to reconnect and backfill. With a webhook, an outage on your side becomes a negotiation with somebody else's retry policy. They will try again for a while, and then they will stop, and the boundary between those two states is documented at best vaguely.

The consequence is that a routine deployment can create a permanent hole. A rolling restart that drops a few seconds of requests, a certificate that expires, a firewall rule that changes: all of these produce failed deliveries that may exhaust their retries before anyone notices.

Treat the retry budget as a countdown, not a safety net

The safe assumption is that a delivery you fail to accept is a delivery you will never see. Keep the slot watermark, run a periodic comparison against an HTTP source, and refill any range where the two disagree. The full procedure is in data gaps and backfill, and it is the component that turns a webhook from a convenient feed into a dependable one.

Verifying that the caller is who it claims

A webhook endpoint is a public URL that accepts data and does something with it. That is a description of an attack surface. Anyone who learns the address can post to it, and if your pipeline treats the body as trusted, they can write whatever they like into your totals.

  • Authenticate every request. Use the provider's secret header or signature scheme, and compare with a constant-time function so the check does not leak the secret through timing.
  • Reject before you parse. An unauthenticated body should never reach a deserialiser, because the deserialiser is itself an attack surface.
  • Serve HTTPS only. A secret sent in a header over plain HTTP is a secret that has been published.
  • Make the path unguessable. A random path segment is not authentication, but it removes the class of attacker who is scanning rather than targeting.
  • Rate limit and cap body size. Both are cheap and both prevent a bad afternoon from becoming an outage.
  • Rotate the secret on a schedule. Support two valid secrets during the overlap so a rotation is not an outage of its own.

Verification also protects the number, not just the server. A payload you did not authenticate is a payload you cannot cite. If a figure from your pipeline is ever questioned, the answer has to include how you know the input was genuine.

When a webhook is the wrong tool

Webhooks are a poor fit whenever you need history, whenever you need microsecond-level immediacy, and whenever the watch list is enormous or changes constantly. They are also a poor fit when the thing you are watching is a state rather than an event: a pool's reserves are better read with an account subscription than inferred from a stream of trades.

They are an excellent fit when you want a small, reliable set of triggers, when you would rather run a web service than a long-lived connection, and when the operational cost of ingestion is not where you want to spend your attention. That last point is why hosted platforms exist at all: choosing an automated Solana volume bot or any other managed tool means consuming somebody else's ingestion decisions, with their retry policy and their idempotency assumptions in place of yours.

Whichever side of that line you sit on, the questions are identical. Which venues does this cover, at which commitment level, with what happens to an event that fails to deliver. A provider who answers those clearly is more useful than one who is marginally faster.

Receiver checklist

  • The handler verifies authentication before it parses anything.
  • The handler persists the raw body and returns success in a single-digit number of milliseconds of your own work.
  • Processing happens in a worker, with retries on your schedule and a dead-letter destination for payloads that never succeed.
  • Every write is keyed on signature plus instruction index, and repeating a delivery changes nothing.
  • Order is derived from slot and in-block position, never from arrival time.
  • A slot watermark advances only when an event is durably stored, and a scheduled job compares it against an HTTP source.
  • The provider's documented commitment level is recorded next to the code, and every figure states which level produced it.
  • A forced endpoint outage has been tested end to end, and the backfill visibly closed the resulting hole.

If your answer to the last line is that you have not tested it, that is the highest-value hour available to you this week. Everything else in this note is theory until a failed delivery proves whether the design holds.

Questions this desk keeps getting

What does at-least-once delivery mean for a Solana webhook?

It means the provider will keep trying until it gets a successful response, and that a delivery you already handled can arrive again. A timeout on your side is indistinguishable from a failure from theirs, so a request you processed perfectly can be retried because your acknowledgement was late. Every receiver in this model must be safe to run twice on the same payload.

Do webhook deliveries arrive in order?

Assume they do not. Retries, parallel delivery workers and network paths all reorder traffic, and almost no provider promises sequencing. Reconstruct order from the data itself using the slot and the position of the transaction, never from arrival time. A pipeline that relies on arrival order will be subtly wrong exactly during the busy periods it was built for.

How quickly does my endpoint need to respond?

Fast enough that the provider does not treat the delivery as failed, which in practice means acknowledging within a small number of seconds. The way to hit that reliably is to do almost nothing in the request handler: verify the caller, write the raw payload to a durable queue, and return success. All decoding, enrichment and aggregation happens afterwards in a worker you control.

What happens if my server is down during a deploy?

The provider retries on its own schedule and eventually stops. The size of the resulting hole is set by a policy you did not write, which is why a webhook-only pipeline is never complete. Keep a slot watermark and an HTTP backfill path so that a deployment window becomes a range you refill rather than data you lost.

Are enhanced or parsed webhook payloads safe to trust?

They are convenient and they are opinionated. A parsed payload means the provider already decided which programs count as a swap, how to attribute amounts and how to treat multi-hop routes. Those decisions are usually sensible and rarely documented exhaustively. Use them to move quickly, and verify the cases that matter against the raw transaction before you publish anything.

How do I verify a webhook really came from my provider?

Use whatever authentication the provider supports, which is normally a shared secret sent in a header or a signature computed over the request body. Compare secrets with a constant-time function, reject anything that fails before you parse the body, and serve the endpoint over HTTPS only. Treat an unauthenticated payload as hostile input rather than as data.

Can I run webhooks and a WebSocket at the same time?

Yes, and for a system that has to be both fast and complete it is a reasonable design. The rule is the same as for any multi-path ingestion: both writers must use one deduplication key, and one of them must be treated as authoritative when the two disagree. Without that, redundancy quietly becomes duplication.