Skip to content

Bucket notifications (webhooks) ​

TinyS3 delivers bucket-notification events to webhooks. Put a NotificationConfiguration on a bucket whose destination is an http(s):// URL, and every matching object create/remove POSTs an S3-shaped event JSON document to that URL.

Configuration goes through the standard S3 API (PutBucketNotificationConfiguration, so aws s3api put-bucket-notification-configuration and every SDK work), or through the console's bucket settings → Notifications tab.

Configuration shape ​

The destination in a QueueConfiguration, TopicConfiguration, or CloudFunctionConfiguration is read as a URL in place of an AWS ARN. Non-URL destinations (real ARNs) are ignored rather than rejected, so a config written for AWS parses to zero webhook rules instead of failing.

bash
aws s3api put-bucket-notification-configuration --bucket my-bucket \
  --notification-configuration '{
    "QueueConfigurations": [{
      "QueueArn": "https://hooks.example.com/s3-events",
      "Events": ["s3:ObjectCreated:*", "s3:ObjectRemoved:*"],
      "Filter": {"Key": {"FilterRules": [
        {"Name": "prefix", "Value": "uploads/"},
        {"Name": "suffix", "Value": ".jpg"}
      ]}}
    }]
  }' --endpoint-url http://localhost:8000

Filters: exact event names (s3:ObjectCreated:Put), wildcards (s3:ObjectCreated:*, s3:*), and key prefix/suffix rules. Events fired: s3:ObjectCreated:Put, s3:ObjectCreated:Copy, s3:ObjectRemoved:Delete, s3:ObjectRemoved:DeleteMarkerCreated.

Event payload ​

A single-record Records array in the AWS event shape (eventVersion 2.1): event name and time, bucket name, object key, and — where the event carries them — size, ETag, and version ID.

Delivery semantics ​

Delivery is asynchronous and best-effort: it runs off the request thread with a short timeout, and a failed POST is logged and dropped — a broken or slow webhook never blocks or fails an S3 request. Redirects are not followed. In-flight deliveries are bounded (256); beyond that, events are dropped with a warning rather than queued without limit. There is no retry or persistent queue — do not build exactly-once pipelines on it.

Internal destinations ​

Webhook URLs are user-supplied, so they pass the same SSRF guard as other outbound features (replication endpoints included): loopback, private-range, and metadata addresses are refused by default. The guard also refuses the ranges the JDK's site-local check misses — carrier-grade NAT (100.64.0.0/10, used by Tailscale, EKS pod networks, and some carrier overlays), the benchmark range (198.18.0.0/15), and the IPv6 transition embeddings (NAT64, 6to4, Teredo, v4-mapped). To deliver to a webhook (or replicate to an endpoint) on any of these internal networks, start the server with:

-Dtinys3.jobs.allowInternalEndpoints=true

The instance-metadata address and other link-local/multicast targets stay refused even with that flag set.

Limitations ​

  • Webhook only. Kafka, AMQP, NATS, Redis, MQTT and other targets are not supported.
  • Best-effort, at-most-once delivery; no retries, no dead-letter queue.
  • No s3:ObjectRestore, replication, or lifecycle event types.

TinyS3 is AGPLv3 licensed. Cloudflare and Cloudflare Workers are trademarks of Cloudflare, Inc. Amazon S3 and AWS are trademarks of Amazon.com, Inc. or its affiliates. MinIO is a trademark of MinIO, Inc. They are named only to describe compatibility; totis.ai is not affiliated with, endorsed by, or sponsored by any of them.