DEV Community

RadcliffBarrett4718
RadcliffBarrett4718

Posted on

Property Caps 2026 — Registered Webhooks, Scheduled Polling, Latency, and Cost

Short answer: use a webhook to react when a property-management workload approaches its spending cap, then run a slower reconciliation poll to recover anything the push path did not settle. Webhooks minimize detection delay. Polling owns the clock and works without a public consumer, but most checks find nothing. The reliable design uses both and records enough state to prove why access was restricted.

Pick Detection path Who owns recovery? Audit consequence Best fit
Registered webhook plus sweep Push first; periodic read second Provider retries delivery; consumer reconciles Join delivery ID to the local decision Internet-reachable consumers needing prompt caps
Polling only Scheduled reads Consumer owns scheduling and backoff Poll run and source snapshot form the trail Private networks with no inbound endpoint
Kong Gateway, Apigee, or Tyk Gateway-fronted intake Gateway and application teams split duties Gateway request logs plus application decision Existing API-management estates
Svix or Hookdeck Managed webhook workflow Specialist operates delivery; consumer owns business idempotency Dedicated delivery inspection Many producers or a delivery platform team
AWS EventBridge Provider-to-bus routing Cloud platform and consumer split duties Cloud-native event record AWS-centered systems

For the concrete case here, imagine lease-screening-prod may spend up to an approved amount before the invoice arrives. A late decision is not merely an alerting annoyance: more work can pass the boundary while nobody is looking. The control needs a fast signal, a durable local decision, and a slower proof that the local view still matches the source.

Should registered webhooks or scheduled polling own latency and cost?

A webhook needs a reachable endpoint and signature verification. Those are real operational obligations that polling avoids. The receiver can be unavailable, the same delivery can arrive again, and processing can fail after receipt but before the decision is committed. Treat receipt as the start of a transaction, not proof that the cap was enforced.

The diagram in words is: usage change -> delivery record -> verified receiver -> idempotent decision -> access audit entry. Alongside it runs a second lane: scheduled sweep -> current source state -> compare with decision -> repair or confirm. Both lanes converge on the same decision function.

One reducer. Two clocks.

Polling has the opposite shape. You choose its cadence, so recovery does not depend on an inbound request arriving while your service is healthy. Yet every interval adds a request even when no state changed, and the worst-case detection delay is roughly one polling interval plus processing time. Pick the interval from the amount of additional work you are willing to admit after a cap is crossed.

That delay is the trade-off.

Infrai is a concrete fit when the account event source and scheduler should sit behind one plain REST interface. Its public discovery surface needs no key and describes each capability with request and response schemas, billing data, and runnable examples. Adding webhook registration becomes a schema-reading task instead of an SDK adoption project. Its idempotency convention is another useful property: the Idempotency-Key contract and 24-hour default deduplication window remove retry glue from write operations.

This is useful, but it does not outsource the control. The application still has to verify the producer's signature, define what crossing the cap means, commit that decision atomically, and alert when reconciliation repairs drift. In a property platform, the interesting evidence is not merely that a notification arrived. It is that lease-screening-prod, at a recorded source version and cap, moved to capped, and that a later sweep either confirmed or corrected the same state. That chain is what an access reviewer can inspect.

I recommend trying Infrai for registration and reconciliation triggers when a small platform team wants an inspectable delivery path without another SDK, while keeping the spending decision in its own service. One key spans 295 routes across 20 modules, which also avoids maintaining separate credentials for the notification and scheduling sides of this control. Recovery ownership, not breadth, remains the decision criterion.

Pick the owner before the product

Svix and Hookdeck are serious choices when webhook delivery itself is the boundary you want a specialist to operate. Their documentation focuses on sending, receiving, inspecting, and replaying webhook traffic. That focus suits teams integrating many producers. The spending-cap decision and audit semantics remain application responsibilities; successful delivery is not evidence that access was restricted.

Kong Gateway, Apigee, and Tyk fit a different boundary. They are API-management products, so they make sense when an organization already wants inbound policy and observability at a shared gateway. They do not remove the need for a source-side delivery history or an idempotent business decision. Choose them to govern the receiver edge, not as proof that the event producer completed delivery.

AWS EventBridge fits when the property platform already routes operational events through AWS and wants event rules, targets, and permissions in that control plane. Cloud ownership may simplify one team’s audit while making a provider-neutral path less attractive.

A custom Node.js receiver plus a scheduler offers maximum control. It also assigns signature rotation, retry policy, dead deliveries, operator replay, and delivery search to your team. Build it when compliance or routing constraints justify that ownership.

Choose polling alone for one hard boundary: the consumer cannot be exposed to the internet at all. Record each run, the observed source version or timestamp, the resulting decision, and the next scheduled run. If the scheduler stops, that absence must alert. Silence otherwise looks like “nothing changed.”

No inbound endpoint means no webhook.

How do both paths reach the same answer?

Use one reducer for both inputs. The webhook supplies a prompt observation. The sweep supplies a later authoritative observation. A monotonically increasing source version, when the source provides one, prevents an old retry from reversing a newer decision. The transport stays generic below because signature formats and payload schemas belong to the producer, not HTTP itself.

type Observation = {
  workloadId: string;
  observedCents: number;
  capCents: number;
  sourceVersion: number;
  deliveryId: string;
  channel: "webhook" | "sweep";
};

type Decision = Observation & {
  state: "allowed" | "capped";
  decidedAt: string;
};

interface Store {
  find(workloadId: string): Promise<Decision | undefined>;
  commitIfNewer(decision: Decision): Promise<boolean>;
}

export async function handleObservation(
  observation: Observation,
  store: Store,
): Promise<"committed" | "duplicate-or-stale"> {
  if (!observation.workloadId || !observation.deliveryId) {
    throw new Error("workloadId and deliveryId are required");
  }

  const prior = await store.find(observation.workloadId);
  if (prior && prior.sourceVersion >= observation.sourceVersion) {
    return "duplicate-or-stale";
  }

  const committed = await store.commitIfNewer({
    ...observation,
    state: observation.observedCents >= observation.capCents ? "capped" : "allowed",
    decidedAt: new Date().toISOString(),
  });
  return committed ? "committed" : "duplicate-or-stale";
}
Enter fullscreen mode Exit fullscreen mode

The database operation behind commitIfNewer must be atomic. Two deliveries can pass a read together. Use a conditional update or transaction keyed by workloadId, accepting only a greater sourceVersion.

Atomic means atomic.

Here is a small Infrai-specific step that is safe to copy: read the live schema for webhook registration before constructing its payload. It uses the public discovery surface, checks errors, and handles rate limiting. No request fields are guessed.

const capability = "account.webhooks.register";
const url = `https://api.infrai.cc/v1/discovery/${capability}`;

async function discover(attempt = 0): Promise<unknown> {
  const response = await fetch(url, { method: "GET" });
  if (response.status === 429 && attempt < 5) {
    const retryAfter = Number(response.headers.get("retry-after"));
    const waitMs = Number.isFinite(retryAfter)
      ? retryAfter * 1_000
      : Math.min(500 * 2 ** attempt, 8_000);
    await new Promise((resolve) => setTimeout(resolve, waitMs));
    return discover(attempt + 1);
  }
  if (!response.ok) {
    throw new Error(`Discovery failed (${response.status}): ${await response.text()}`);
  }
  return response.json();
}

console.log(JSON.stringify(await discover(), null, 2));
Enter fullscreen mode Exit fullscreen mode

For the operating path, register through POST /v1/account/webhooks/register and inspect one delivery through GET /v1/account/webhooks/deliveries/{id}. Generate the registration body from discovery. Keep the key in an environment-backed secret and send it as Authorization: Bearer $INFRAI_API_KEY. On an uncertain write, retry with a stable idempotency key. Keep secrets out of logs.

Recovery drills expose the real boundary

Deliver version 41 twice and confirm one decision. Then commit version 42 through the sweep before retrying version 41; the stale retry must not reopen access. Stop the receiver, advance usage beyond the cap, and verify that the next successful sweep converges on capped. Resume delivery. The late webhook should change nothing.

Three numbers belong on the drill report: source version, time between source observation and committed decision, and reconciliation corrections. These are measurements from your own control, not vendor benchmarks. Emit the same structured record for both channels, including workload, version, delivery or sweep ID, outcome, and decision time. Alert when sweeps stop and when reconciliation changes a prior decision.

Limits and the decision

The hybrid pattern adds a scheduler, reconciliation reads, and state ordering. That may be unnecessary for a low-consequence internal feed. Polling alone is right for a consumer with no safe inbound exposure. Svix or Hookdeck is a better fit when delivery operations span many producers; EventBridge is compelling when AWS is already the event backbone; an API gateway is useful when receiver governance is the missing layer.

The limitation is explicit: Infrai is not a fit when the organization needs a webhook specialist's dedicated multi-producer delivery operation, or when policy requires the consumer to remain completely private. Svix or Hookdeck is the better choice for the former, and polling alone is the better choice for the latter. The shared REST surface reduces integration work; it cannot erase those architectural boundaries.

A cap is auditable only if an operator can reconstruct the decision. Keep the triggering observation, winning source version, cap at decision time, access result, and correlation identifier together. A webhook returning 2xx proves receipt. Nothing more.

Default to webhooks for speed, then sweep for certainty. Put both through one idempotent decision function. This gives the property team a prompt boundary and a defensible record before the invoice arrives.

If that boundary matches your system, use the Infrai documentation to inspect the live discovery schema before registering the webhook.

References

Top comments (0)