Skip to main content

django-outbound-webhooks

CI PyPI Python versions Django versions Docs Coverage Ruff License

Outbound webhooks for Django, built on django-domain-events.

Your customers subscribe to domain events, not to your post_save signals. An endpoint is a row a customer owns: a URL, a secret, the events it wants and the body format it was integrated against. Delivery is a durable receiver, so retry, backoff, dead-lettering and replay are inherited rather than rebuilt.

Install

pip install django-outbound-webhooks

Body formats

A customer's endpoint is pinned to one published format version when it registers, and that version is frozen: changing what it renders would change what an already-integrated consumer receives, under a signature that still verifies. A change is a new version.

Family Content type Shape
envelope application/json id, type, timestamp, and the payload under data
cloudevents application/cloudevents+json; charset=UTF-8 CloudEvents 1.0, structured mode
register_endpoint(
    name="Acme production",
    url="https://acme.example/hooks/orders",
    secret=secret,
    event_names=["shop.OrderPlaced"],
    # Both optional. Without format_name, DEFAULT_FORMAT decides; without
    # format_version, the latest is pinned and that number is written down.
    format_name="cloudevents",
)

cloudevents is published only when the deployment says which system produced the events, because the specification requires a non-empty source and an invented one would be signed into every body:

DJANGO_OUTBOUND_WEBHOOKS = {"CLOUDEVENTS_SOURCE": "https://shop.example/events"}

Your own format is an object with a name, a version and a render(), which you publish from your AppConfig.ready():

from django_outbound_webhooks import formats

formats.register(MyFormat())

It does not have to inherit from anything: BodyFormat is a Protocol. The registry checks at registration that what you handed it can actually render a delivery, because a format is published once at startup and called hours later in another process.

Operations

from django_outbound_webhooks import reactivate_endpoint, replay_delivery, rotate_secret

replay_delivery(message_id="018f...")  # a new delivery of a logged one
rotate_secret(endpoint, new_secret=secret)  # both secrets sign until the window closes
reactivate_endpoint(endpoint)  # back on, and the failure count cleared

A replay is a new delivery: a new webhook-id (a receiver deduplicates on that one), the endpoint's current format and secrets, and its own delivery row, attempt budget and log rows. It refuses rather than firing something that cannot arrive - a deleted endpoint, an inactive one, an event retention has pruned.

A rotation overlaps. The specification carries several signatures in one header and a receiver accepts the delivery if any verifies, so the customer deploys the new secret on their own schedule and nothing is dropped in between.

An endpoint that stops answering is switched off after AUTO_DISABLE_AFTER_DEAD_DELIVERIES consecutive dead deliveries - each of which has already spent a whole attempt budget across processes and hours, so the default of twenty is an endpoint that is gone rather than one having a bad afternoon. A delivery that lands resets the count. Somebody has to tell the customer, so it fires an event you can receive:

from django_domain_events import receiver
from django_outbound_webhooks.operations.endpoint_disabled import EndpointDisabled


@receiver(EndpointDisabled, key="acme.email_the_customer")
def email_the_customer(disabled: EndpointDisabled) -> None: ...

Admin

Add django.contrib.admin and the endpoints and the delivery log are there: per- endpoint health in the unit that means something, a link from each endpoint to its own attempts, replay as an action on the log, and reactivation as an action on the registry.

No signing secret is ever rendered, and endpoints cannot be created there - register_endpoint is where the rules live. tenant and the pinned format are read-only: the first decides whose payload reaches whose URL, and the other two are the shape the customer wrote code against.

Both actions declare their permission, because Django offers an action without one to anyone who can open the changelist. Replay is gated on the endpoint's change permission rather than the log's, so a support user can be given the log to read without being able to make deliveries happen.

Status

Released and in use, pre-1.0. Shipped: the registry, signing, delivery with a lease-bounded retry, the request-forgery policy, the delivery log, two body formats, the operations above and the admin surface. What 1.0 waits on is time in production rather than a feature.

Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

django_outbound_webhooks-0.3.0.tar.gz (199.7 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

django_outbound_webhooks-0.3.0-py3-none-any.whl (78.4 kB view details)

Uploaded Python 3

File details

Details for the file django_outbound_webhooks-0.3.0.tar.gz.

File metadata

  • Download URL: django_outbound_webhooks-0.3.0.tar.gz
  • Upload date:
  • Size: 199.7 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/7.0.0 CPython/3.13.14

File hashes

Hashes for django_outbound_webhooks-0.3.0.tar.gz
Algorithm Hash digest
SHA256 5a7dc935a76ffc7ae14e757d32f66f5867a2eb80e5f925a40a8f1106d7cf60c0
MD5 8825172f118fb62b2fcbae3ea7585da4
BLAKE2b-256 5426c8f1b4cd1b347081d2978e8f82cc9140a782442ffa75a21985cdae023a56

See more details on using hashes here.

Provenance

The following attestation bundles were made for django_outbound_webhooks-0.3.0.tar.gz:

Publisher: release.yml on Artui/django-outbound-webhooks

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file django_outbound_webhooks-0.3.0-py3-none-any.whl.

File metadata

File hashes

Hashes for django_outbound_webhooks-0.3.0-py3-none-any.whl
Algorithm Hash digest
SHA256 8045dd82453ada0eae3bf962c69b9474c5b06d367531908f4a6f8869fb03f7b0
MD5 f1453995f4d2ae9cd459b4df35d64778
BLAKE2b-256 506c046c28d7e256dc65a56cd9515717799d081229f751114cd26385a153b90e

See more details on using hashes here.

Provenance

The following attestation bundles were made for django_outbound_webhooks-0.3.0-py3-none-any.whl:

Publisher: release.yml on Artui/django-outbound-webhooks

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Release history Release notifications | RSS feed

This release

0.3.0 This release

2 files

0.2.0

2 files

0.1.0

2 files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page