Delivery & Retries
When watched addresses appear on-chain, Spectrum sends an HTTPS POST to your webhook URL. A single request carries every matching event from one block. This page covers the delivery payload and headers your server receives, how retries and dead-lettering work, the two behaviors your receiver must handle (idempotency and reorgs), and the delivery tools in the dashboard.
The delivery payload
Block-level fields live on the event envelope so they aren’t repeated per event. The event.activity[] array holds one entry per matched event, ordered by (txIndex, logIndex, subIndex).
{
"id": "whevt_a1b2c3d4e5f6",
"type": "ADDRESS_ACTIVITY",
"webhookId": "wh_4dbc2155930fa7054493aed0",
"createdAt": "2026-05-08T12:34:56.789Z",
"event": {
"chain": "ethereum",
"blockHash": "0x...",
"blockNumber": 19000000,
"blockTimestamp": 1700000000,
"removed": false,
"activity": [
{
"category": "token",
"fromAddress": "0x...",
"toAddress": "0x...",
"hash": "0x...",
"txIndex": 12,
"rawValue": "1000000",
"value": "1",
"asset": { "symbol": "USDC", "decimals": 6, "address": "0xa0b86991..." }
}
]
}
}Envelope fields
| Field | Description |
|---|---|
id | Logical batch id (whevt_…). Identifies this block-batch; stable across a reorg rollback for the same batch. |
type | The webhook type, e.g. ADDRESS_ACTIVITY. |
webhookId | The webhook that matched (wh_…). Matches the X-Spectrum-Webhook-Id header. |
createdAt | When the delivery was generated (RFC3339). |
event.chain | Chain slug. |
event.blockHash / event.blockNumber | The block these events came from. |
event.blockTimestamp | Unix seconds. |
event.removed | true on a reorg rollback (see below), otherwise false. |
event.activity[] | One entry per matched event. |
Activity entry fields
Each entry includes a self-describing core; additional fields appear depending on the event’s category. See Activity Categories for the fields each category carries.
| Field | Description |
|---|---|
category | external, internal, token, erc721, or erc1155. |
fromAddress / toAddress | Lowercase hex addresses. |
hash | Transaction hash. |
txIndex | Transaction index within the block (decoded integer). |
rawValue | Raw base-unit amount as a string (no decimals applied). |
value | rawValue ÷ 10^asset.decimals as an exact decimal string, or null if decimals aren’t resolved yet. |
asset | { symbol, decimals, address? }. address is present for token categories. |
tokenId | NFT token id (raw integer string), for erc721/erc1155. |
logIndex, log, subIndex, typeTraceAddress | Category-specific fields, present for some categories. See Activity Categories. |
Wire conventions
- Addresses are lowercase hex everywhere (
fromAddress,toAddress,asset.address). rawValueis the exact base-unit integer as a string;valueis the decimal-adjusted amount as a string to avoid float precision loss.valueisnulluntil token decimals are known.blockNumber,txIndex,logIndexare decoded integers, re-encode to hex yourself if you need it.tokenIdis a raw integer string.
Delivery headers
X-Spectrum-Signature: t=<unix>,v1=<hmac-sha256-hex>
X-Spectrum-Signature-Previous: t=<unix>,v1=<hmac> (only during a key rotation grace window)
X-Spectrum-Attempt: 1
X-Spectrum-Event-Id: dq_a1b2c3d4e5f6a1b2c3d4
X-Spectrum-Webhook-Id: wh_4dbc2155930fa7054493aed0| Header | Purpose |
|---|---|
X-Spectrum-Signature | HMAC-SHA256 signature, verify every delivery. See Signing & Verification. |
X-Spectrum-Signature-Previous | Present only during a signing-key rotation grace window. |
X-Spectrum-Attempt | Attempt number for this delivery, starting at 1. |
X-Spectrum-Event-Id | The idempotency key (dq_…). Stable across retries of the same delivery. |
X-Spectrum-Webhook-Id | The webhook id, matches webhookId in the body. |
Return a 2xx status once you’ve accepted the delivery. Any non-2xx (or a timeout) is treated as a failure and retried.
Retry policy
When your endpoint doesn’t return a 2xx, Spectrum retries with backoff. Both values are set per webhook from its settings.
| Setting | Default | Range | Behavior |
|---|---|---|---|
| Max delivery attempts | 5 | 0 or a positive integer | Attempts before the delivery is dead-lettered. 0 means retry forever. |
| Retry interval | 60s | 1s to 1 day | Base interval between retry attempts. |
- A delivery is considered successful when your server responds with a
2xx. - After the max attempts are exhausted, the delivery moves to the dead-letter queue. You can replay dead-lettered deliveries later.
- Because retries can re-send a delivery, receivers must be idempotent (below).
Idempotency
Deliveries are at-least-once. The same delivery can arrive more than once, for example when your server accepts it but the connection drops before Spectrum records the 2xx, or after a worker restart.
Deduplicate on the X-Spectrum-Event-Id header. It is stable across every retry of the same delivery, so recording processed ids and dropping repeats makes your handler safe.
Don’t dedupe on the body’s id (whevt_…), that’s a logical batch id, not a per-delivery key, and it is intentionally reused for reorg rollbacks (below) and shared across a split batch.
Reorgs
When a block that was already delivered gets orphaned by a chain reorganization, Spectrum sends a rollback delivery with event.removed: true. The body carries the same batch id and the same activity[] as the original, so you can reverse-apply whatever state you derived from it.
{
"id": "whevt_a1b2c3d4e5f6",
"type": "ADDRESS_ACTIVITY",
"webhookId": "wh_4dbc2155930fa7054493aed0",
"event": {
"chain": "ethereum",
"blockHash": "0x<orphaned>",
"blockNumber": 19000000,
"removed": true,
"activity": [ /* same entries as the original delivery */ ]
}
}How to handle it:
- Treat
event.removed === trueas a reverse-apply signal, undo any state changes made from the original delivery. - The rollback arrives as a separate delivery, so its
X-Spectrum-Event-Idheader is a new value. Correlate the rollback with the original by matching the bodyid(andevent.blockHash+activity[]), not the header id. - If you ignore the flag, your idempotency dedupe on the body
idwill treat the rollback as a duplicate and drop it, degraded but not unsafe.
Live reorg detection covers depths up to 64 blocks. Deeper or during-downtime reorgs are handled best-effort on listener recovery.
Large blocks (batch splitting)
If a single block produces more than 100 matching events for one webhook, Spectrum splits them across multiple POSTs. Each split has the same event.blockHash and event.blockNumber but a different body id. Correlate splits by block.
Sending a test delivery
From a webhook’s page in the dashboard, send a test delivery. Spectrum sends a real, signed POST to your configured URL, just like a live delivery, so you can confirm your receiver verifies the signature and returns a 2xx. The result appears in delivery history within a few seconds.
Delivery history
The dashboard records every delivery attempt for a webhook, so you can debug failures and confirm successes. Each attempt shows:
| Column | Description |
|---|---|
| Attempted at | When the attempt was made. |
| Success | Whether your endpoint returned a 2xx. |
| Status code | The HTTP status your endpoint returned. 0 means no HTTP response (timeout or network error). |
| Attempt number | Which attempt this was, starting at 1. |
| Duration | How long the attempt took. |
| Category / chain / asset / matched addresses | Context about the event delivered. |
| Error message | Failure detail, when the attempt failed. |
Monitoring stats
The dashboard shows delivery stats per webhook and across your whole account, over a window you choose (e.g. last hour, 24 hours, 7 days):
- Total attempts, successes, failures, and success rate.
- Average and p95 delivery latency.
- Breakdowns by category and by chain.
Pausing and resuming
Pause a webhook to stop deliveries without deleting it, useful during receiver maintenance. Toggle it back to active from the same place to resume delivery.
Replaying failed deliveries
When deliveries exhaust their retry policy they move to the dead-letter queue. After you’ve fixed your endpoint, replay them from the webhook’s page; they move back to pending and Spectrum retries them.
Replaying re-sends deliveries that previously failed. Make sure your receiver is idempotent (dedupe on X-Spectrum-Event-Id) before replaying. See Idempotency.
Audit history
Every change to a webhook (create, edit, delete, address changes, key rotation, unpause, and replay) is recorded in the dashboard’s audit log, with the action, who made it, and when. Use it for activity timelines and support investigations.