---
title: "Ordered message of one subscription, carrying data and control events."
method: POST
path: "push.events"
---

# Ordered message of one subscription, carrying data and control events.

`POST push.events` (webhook)

Authenticate with the `webhook-signature` check, not our API credentials: pick the secret by `bv-subscription-id` only among subscriptions you created, reject unknown ids before any processing, verify over the raw body in constant time, and allow about 5 minutes of timestamp skew. A retry of the same batch (same block range, positions and events) keeps `webhook-id` and only re-signs the headers; when the boundaries or content change (batch recut after a crash, recovery after a failure, re-delivery after an incident) the `webhook-id` changes, so deduplicate by event `id`, not by `webhook-id`. One URL receives up to as many concurrent requests as the subscription has chains; keep progress per `data.chain`.
Record progress per chain with `complete_through_block` (a block can span messages). Deduplicate within a subscription by event `id`, across subscriptions by `ref` and `type`.
When receiving `chain.reorg`: it is the reorg notice for blocks already delivered on that stream (the replaced range is `from_block` to `to_block`, depth at least N, at most 1,024 blocks). It is free and does not mean data loss. At N=1, reorgs are normal on Ethereum and Sepolia. It is followed immediately by automatic re-delivery of the canonical events with new event `id`s. Receivers should drop or mark events of the replaced range by `ref`, keep the re-delivered ones, and deduplicate by event `id`. If a reorg is deeper than 1,024 blocks or a restart encounters a progress mismatch, that chain halts (shown as `halted` in `GET /push/chains`), and re-delivery follows after operators restore it. `complete_through_block` goes back only after `replay` or after incident recovery. Ignore event types you do not know. A message you cannot process can be answered 2xx and handled later; there is no skip operation.

## Headers

- `webhook-id` string, required
- `webhook-timestamp` string, required
- `webhook-signature` string, required
- `bv-subscription-id` string, required: The subscription id as decimal text, for the bv-subscription-id header. The value range is the same as SubscriptionId (1 to 2147483647).

## Payload

- PushEventsMessage
  - `type` 'push.events', required
  - `created_at` string, required
  - `data` object, required
    - `subscription_id` integer, required: The subscription id: the one number that identifies a subscription everywhere (PostgreSQL integer identity, fits u32, never reused). A plain JSON integer in bodies, decimal digits in the path.
    - `chain` string, required: lower(chain_slug), ES-030 rule 10.
    - `complete_through_block` integer, required
    - `events` Event[], required
      - union
        - NativeTransfer: Successful top-level transaction with value > 0 where a watched address is from or to (for a contract creation, to is the new contract). Internal transfers are not delivered.
          - `id` string, required
          - `type` 'native.transfer', required
          - `ref` string, required
          - `from` string, required
          - `to` string, required
          - `amount` string, required
          - `block_number` integer, required
          - `block_hash` string, required
          - `block_timestamp` string, required
          - `tx_hash` string, required
          - `tx_index` integer, required
          - `matched` TransferMatch[], required
            - `address` string, required
            - `role` 'from' | 'to', required
        - TokenTransfer: ERC-20 Transfer (3 topics), ERC-721 Transfer (4 topics) or ERC-1155 TransferSingle / TransferBatch of any contract, where a watched address is from or to. One event per (id, value) of a TransferBatch (batch_index from 0).
          - `id` string, required
          - `type` 'token.transfer', required
          - `ref` string, required
          - `standard` 'erc20' | 'erc721' | 'erc1155', required
          - `token` string, required
          - `from` string, required
          - `to` string, required
          - `token_id` string, nullable, required: null for erc20.
          - `amount` string, required
          - `batch_index` integer, nullable, required: Only for an ERC-1155 TransferBatch.
          - `block_number` integer, required
          - `block_hash` string, required
          - `block_timestamp` string, required
          - `tx_hash` string, required
          - `tx_index` integer, required
          - `log_index` integer, required
          - `matched` TransferMatch[], required
            - `address` string, required
            - `role` 'from' | 'to', required
        - LogEvent: Any other log that names a watched address, as the emitting contract or in topics[1] to topics[3] (left-padded to 32 bytes).
          - `id` string, required
          - `type` 'log', required
          - `ref` string, required
          - `address` string, required
          - `topics` Hash32[], required
          - `data` string, required
          - `block_number` integer, required
          - `block_hash` string, required
          - `block_timestamp` string, required
          - `tx_hash` string, required
          - `tx_index` integer, required
          - `log_index` integer, required
          - `matched` object[], required
            - `address` string, required
            - `role` 'emitter' | 'topic1' | 'topic2' | 'topic3', required
        - SubscriptionGap: No valid delivery for from_block..to_block on this chain; rescan it yourself or replay. Free.
          - `id` string, required
          - `type` 'subscription.gap', required
          - `from_block` integer, required
          - `to_block` integer, required
          - `reason` 'retention_expired' | 'startup_history_unavailable', required: startup_history_unavailable: the range skipped because matching history at startup had expired; backfill it with the Data API or eth_getLogs.
        - ChainReorg: Reorg notice for blocks already delivered on this stream: the range from_block to to_block is the replaced range (depth at least N, at most 1,024 blocks). Followed by automatic re-delivery of the canonical events with new event ids without stopping the chain or requiring operator intervention. At N=1, reorgs are normal on Ethereum and Sepolia. What the receiver should do: drop or mark events of the replaced range by ref, keep the re-delivered ones, and deduplicate by event id. This control event is free and does not mean data loss (does not advance complete_through_block). For deep reorgs (> 1,024 blocks) or when a restart finds a progress mismatch, the chain stops releasing blocks (shown as halted in GET /push/chains), and re-delivery follows after operators restore it. expected_hash and observed_hash are the hash previously delivered for from_block and the hash now reported on the canonical chain.
          - `id` string, required
          - `type` 'chain.reorg', required
          - `from_block` integer, required
          - `to_block` integer, required
          - `expected_hash` string, required
          - `observed_hash` string, required

## Acknowledgement `2XX`

Any 2xx within 10 s, after durable processing. 3xx is a failure; 410 is retried like any failure; 429 Retry-After is honored; anything else is retried.

---

[API](https://skmtc.dev/blockvectra/apis/blockvectra-push-api.md) · [All operations](https://skmtc.dev/blockvectra/apis/blockvectra-push-api/llms.txt) · [OpenAPI document](https://skmtc.dev/blockvectra/apis/blockvectra-push-api/revisions/396dd7621c54?raw)
