Skip to main content

Webhooks

Webhooks push resource lifecycle events to an HTTPS endpoint you control. They are driven by the platform's internal event pipeline, not by your API calls, so they fire whether a change originated from the API, the web app or the mobile app.

Registering an endpoint

curl -X POST https://api.filedgr.network/notifications/webhooks \
-H "x-api-key: $FILEDGR_API_KEY" \
-H "x-api-secret: $FILEDGR_API_SECRET" \
-H "Content-Type: application/json" \
-d '{
"url": "https://example.com/hooks/filedgr",
"event_types": ["vault.created", "vault.completed", "signature.completed"],
"description": "Production listener",
"secret": "a-long-random-string"
}'
FieldTypeRequiredNotes
urlstringyesSee destination rules
event_typesstring[]yesNon-empty; each must be a known event type. No wildcards
descriptionstringnoFree text
secretstringnoHMAC signing key. Strongly recommended
custom_headersobjectnoMerged into the request headers, and may override the standard ones
resource_filtersobjectnoSee filters

Manage them with GET/PUT/DELETE /notifications/webhooks/{config_id}, and inspect attempts with GET /notifications/webhooks/{config_id}/deliveries.

warning

secret is stored and returned in plaintext by GET /notifications/webhooks. Anyone who can read your webhook configuration can forge signed deliveries. Scope your API credentials accordingly.

Event types

ResourceEvents
Vaultvault.created vault.completed vault.failed vault.error vault.deleted
Streamstream.created stream.completed stream.failed stream.error stream.deleted
Data attachmentdata_attachment.created data_attachment.completed data_attachment.failed data_attachment.deleted
Signaturesignature.created signature.completed signature.failed signature.error
Assetasset.created asset.deployed asset.error
Vault permissionvault.permission.granted vault.permission.revoked vault.permission.updated
Stream permissionstream.permission.granted stream.permission.revoked stream.permission.updated
note

Registration also accepts vault.updated, stream.updated, data_attachment.updated, data_attachment.received, signature.updated and asset.updated. These are currently never emitted — subscribing to them is accepted but will not deliver anything. Build against the table above.

Events are derived from resource status transitions. Intermediate pipeline states produce no event, so you will see a *.created and later a *.completed, not a message per internal step.

Delivery payload

{
"event_type": "vault.created",
"timestamp": "2026-08-26T12:00:00.123456",
"data": { "id": "...", "name": "...", "status": "FILEDGR_RECEIVED" }
}

data is the full resource payload for the event. timestamp is UTC but is naive — it carries no Z suffix and no offset. Parse it as UTC explicitly.

Request headers

HeaderValue
Content-Typeapplication/json
User-AgentFiledgr-Webhook/1.0
X-Filedgr-EventThe event type
X-Filedgr-Signaturesha256=<hex> — only present when secret is set
warning

There is no delivery id and no timestamp header, so signatures are not replay-protected. Deduplicate on data.id combined with event_type, and reject events you have already processed.

Verifying signatures

The signature is an HMAC-SHA256 over the raw request body, keyed with your secret.

import hmac, hashlib

def is_valid(raw_body: bytes, header: str, secret: str) -> bool:
expected = "sha256=" + hmac.new(secret.encode(), raw_body, hashlib.sha256).hexdigest()
return hmac.compare_digest(expected, header)
const crypto = require("crypto");

function isValid(rawBody, header, secret) {
const expected =
"sha256=" + crypto.createHmac("sha256", secret).update(rawBody).digest("hex");
return crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(header));
}
danger

Hash the raw bytes exactly as received. Re-serialising the parsed JSON will change key order and whitespace, and the signature will not match.

Retries and failure handling

  • Up to 3 attempts per event, with a 30 second timeout each.
  • Backoff between attempts is 2s then 4s.
  • A 4xx other than 429 aborts the inline retry loop after the first attempt.
  • 5xx, 429, timeouts and connection errors consume all three attempts.

A background job also re-sends deliveries that are still failed, were attempted within the last 24 hours, and have recorded fewer than 3 attempts.

warning

Because a 4xx abort records only one attempt, that event is picked up by the background job and re-sent over the following minutes. A 4xx delays delivery rather than permanently discarding it, so make your handler idempotent.

Return a 2xx as soon as you have durably accepted the event, and process it asynchronously — anything slower than 30 seconds counts as a failure.

Auto-disable

Each failed delivery increments a counter; each success resets it to zero. At 10 consecutive failures the webhook's status flips to DISABLED and deliveries stop. Re-enable it with:

curl -X PUT https://api.filedgr.network/notifications/webhooks/$CONFIG_ID \
-H "x-api-key: $FILEDGR_API_KEY" -H "x-api-secret: $FILEDGR_API_SECRET" \
-H "Content-Type: application/json" -d '{"status": "ACTIVE"}'

Status values are ACTIVE, PAUSED and DISABLED. Only ACTIVE webhooks receive deliveries.

Deduplication window

Identical events — same entity, same resource, same event type — are suppressed if one was already recorded within the previous 5 minutes. The check runs before every channel, so it affects webhooks too.

note

The deduplication record lives in the in-app notification table. If in-app notifications are disabled by preferences or hit their rate limit, no record is written and nothing is suppressed — so deduplication is not something you can rely on. Deduplicate on your side regardless.

Destination rules

Delivery targets are validated before any request is made. A URL is rejected outright if:

  • the scheme is not http or https;
  • the port is not 80 or 443;
  • the hostname resolves to a private, loopback, link-local, reserved, multicast or unspecified address.

Blocked attempts appear in delivery history with no status code and the message blocked: destination not allowed.

tip

This is the usual reason a webhook "never fires" during local testing — tunnels pointing at localhost and private addresses are refused. Test against a publicly resolvable host.

Use HTTPS in production. Plain http on port 80 is accepted, but your payloads and signatures would travel in the clear.

Resource filters

Restrict a webhook to specific resources:

{ "resource_filters": { "vault_ids": ["<uuid>"], "stream_ids": ["<uuid>"] } }
danger

Filters only understand vault_ids and stream_ids. If resource_filters is set, events for any other resource type — data_attachment.*, signature.*, asset.*match nothing and are silently dropped. Register a second, unfiltered webhook for those event types.

Webhooks ignore notification preferences

Notification preferences govern the email, in-app and push channels. Webhook delivery is never gated by them — disabling all notifications does not stop webhooks. Delete or disable the webhook configuration itself.