{"result":{"depiction_url":null,"edges":{"in_module":{"objects":[{"created":"2026-09-29T13:07:54Z","object_id":{"id":2703,"is_a":["text","documentation","reference","module"],"name":"doc_module_mod_websub","title":"mod_websub","uri":"https:\/\/zotonic.com\/id\/2703"},"seq":1}],"predicate":{"id":333,"is_a":["meta","predicate"],"name":"in_module","title":{"_type":"trans","tr":{"en":"In module"}},"uri":"https:\/\/zotonic.com\/id\/in_module"}},"subject":{"objects":[{"created":"2026-09-29T13:07:54Z","object_id":{"id":2555,"is_a":["categorization","keyword","keyword_information_type"],"name":"zotonic_topic_reference","title":"Reference","uri":"https:\/\/zotonic.com\/id\/2555"},"seq":1},{"created":"2026-09-29T13:07:54Z","object_id":{"id":2565,"is_a":["categorization","keyword","keyword_audience"],"name":"zotonic_topic_integrator","title":"Integrator","uri":"https:\/\/zotonic.com\/id\/2565"},"seq":2},{"created":"2026-09-29T13:07:54Z","object_id":{"id":2592,"is_a":["categorization","keyword","keyword_domain"],"name":"zotonic_topic_authorization_and_access_control","title":"Authorization and access control","uri":"https:\/\/zotonic.com\/id\/2592"},"seq":3},{"created":"2026-09-29T13:07:54Z","object_id":{"id":2599,"is_a":["categorization","keyword","keyword_domain"],"name":"zotonic_topic_api_and_integration","title":"API and integration","uri":"https:\/\/zotonic.com\/id\/2599"},"seq":4},{"created":"2026-09-29T13:07:54Z","object_id":{"id":2601,"is_a":["categorization","keyword","keyword_domain"],"name":"zotonic_topic_export_and_syndication","title":"Export and syndication","uri":"https:\/\/zotonic.com\/id\/2601"},"seq":5},{"created":"2026-09-29T13:07:54Z","object_id":{"id":2625,"is_a":["categorization","keyword","keyword_architecture"],"name":"zotonic_topic_model","title":"Model","uri":"https:\/\/zotonic.com\/id\/2625"},"seq":6},{"created":"2026-09-29T13:07:54Z","object_id":{"id":2676,"is_a":["categorization","keyword","keyword_technology"],"name":"zotonic_topic_http","title":"HTTP","uri":"https:\/\/zotonic.com\/id\/2676"},"seq":7}],"predicate":{"id":308,"is_a":["meta","predicate"],"name":"subject","title":{"_type":"trans","tr":{"en":"Keyword"}},"uri":"http:\/\/purl.org\/dc\/elements\/1.1\/subject"}}},"id":2712,"is_a":["text","documentation","reference","model"],"links":[{"rel":"self","target":"https:\/\/zotonic.com\/.zotonic\/websub\/topic\/2712"},{"rel":"hub","target":"https:\/\/zotonic.com\/.zotonic\/websub"}],"medium":null,"medium_url":null,"name":"doc_model_model_websub","page_url":{"en":"https:\/\/zotonic.com\/docs\/2712\/websub","x-default":"https:\/\/zotonic.com\/docs\/2712\/websub"},"preview_url":null,"resource":{"version":90,"pivot_location_lat":null,"title":"websub","is_authoritative":true,"body":"<p>Model for WebSub resource subscriptions, delivery queues, and automatic imports.<\/p>\n<h2>Available model API paths<\/h2>\n<table class=\"table\"><thead><tr><th>Method<\/th><th>Path<\/th><th>Result<\/th><\/tr><\/thead><tbody><tr><td><code>get<\/code><\/td><td><code>\/failed_hostnames<\/code><\/td><td>Sorted, distinct failed remote hostnames; requires <code>use mod_websub<\/code>. Payload: <code>type<\/code> (<code>all<\/code>, <code>export<\/code>, <code>import<\/code>).<\/td><\/tr><tr><td><code>get<\/code><\/td><td><code>\/subscriptions<\/code><\/td><td>Paginated overview; requires <code>use mod_websub<\/code>. Payload: <code>type<\/code> (<code>all<\/code>, <code>export<\/code>, <code>import<\/code>), numeric local <code>rsc_id<\/code>, <code>hostname<\/code>, <code>status<\/code>, <code>errors<\/code> (<code>yes<\/code>\/<code>no<\/code>\/<code>all<\/code>), and <code>page<\/code>.<\/td><\/tr><tr><td><code>get<\/code><\/td><td><code>\/subscriber_count\/+id<\/code><\/td><td>Number of unexpired incoming subscriptions; requires resource edit permission.<\/td><\/tr><tr><td><code>get<\/code><\/td><td><code>\/status\/+id<\/code><\/td><td>Latest import subscription status for an editable resource, or <code>undefined<\/code> when none exists.<\/td><\/tr><\/tbody><\/table>\n<p><code>+id<\/code> is resolved through <code>m_rsc:rid\/2<\/code>. The template equivalent is\n<code>m.websub.status[id]<\/code>; the model checks resource edit permission independently of\nthe template or controller. Unauthorized reads return <code>eacces<\/code>. There are no\npublic model POST or DELETE paths for starting or stopping subscriptions.<\/p>\n<p>The overview includes retained expired\/stopped subscriptions and renewal generations,\n50 rows per page. Hostname is an exact, case-insensitive match on the incoming\ncallback host or outgoing source host, ignoring ports and a trailing DNS dot.\nStatus accepts <code>active<\/code>, <code>pending<\/code>, <code>expired<\/code>, or <code>stopped<\/code>; empty\/<code>all<\/code> means all\nstates. The errors filter selects rows with or without the table’s error indicator.\nAll filters apply before pagination and counting. The overview returns a\n<code>search_result<\/code> record with an exact total, capped at 10,000 pages.\nIt returns remote hostnames, never callback URLs, tokens, secrets,\nor authentication data. Invalid filters return <code>is_invalid<\/code> without broadening the query.<\/p>\n<p>Status includes desired state (<code>is_enabled<\/code>), confirmation state\n(<code>is_unsubscribed<\/code>, <code>is_active<\/code>), <code>pending_mode<\/code>, <code>lease<\/code>, <code>last_received<\/code>,\n<code>last_error<\/code>, and <code>credential_error<\/code>. During renewal, <code>is_active<\/code> also accounts\nfor a still-active predecessor callback. Status never exposes callback tokens,\nHMAC secrets, or source OAuth2 tokens.<\/p>\n<h2>Erlang API<\/h2>\n<ul><li><code>subscribe\/2<\/code> starts a durable subscription for an editable, non-authoritative\nresource with a remote URI. It requires a logged-in user and is idempotent for\nan already-enabled subscription to that source. Discovery and verification run\nasynchronously; <code>ok<\/code> means intent was recorded, not that the hub confirmed it.<\/li><li><code>unsubscribe\/2<\/code> requires edit permission, immediately disables automatic imports,\ndiscards queued updates, and schedules remote unsubscription.<\/li><li><code>topic_url\/2<\/code> generates the language-independent JSON topic URL. Use\n<code>m_rsc:uri\/2<\/code> for the resource&#39;s semantic <code>\/id<\/code> identity instead.<\/li><\/ul>\n<pre class=\"notranslate\"><code class=\"notranslate language-erlang\">ok = m_websub:subscribe(Id, Context).\nok = m_websub:unsubscribe(Id, Context).\n<\/code><\/pre>\n<p>Admin postbacks call these checked functions. The remaining exported queue,\nverification, export-registration, schema, and maintenance functions are internal\nintegration APIs. They are not exposed through model paths. In particular,\n<code>update_export\/6<\/code> and <code>delete_export\/3<\/code> are called after the hub has verified intent;\ncallers must not use them to bypass authorization or callback verification.<\/p>\n<h2>Delivery and import processing<\/h2>\n<p>Publisher queues coalesce resource versions, enforce lease expiry, and recheck\ncurrent access within the original authentication&#39;s group restrictions. Delivery\ncontains the complete JSON topic representation and a signature when a secret was\nsupplied. Exhausted retries discard the notification and mark the subscription\nas errored with <code>retry_timeout<\/code>. The confirmed lease is retained; later resource\nupdates can start a new delivery window. Retries back off quadratically from five\nminutes to twelve hours, ending one week after the first attempt or at lease expiry,\nwhichever comes first. Newer queued versions preserve the window and backoff;\nsuccessful delivery clears them.<\/p>\n<p>Subscriber callbacks identify the subscription by capability token and validate\nthe signed resource URI. Public imports consume the pushed JSON; credentialed\nimports refetch through <code>z_fetch<\/code> using the subscribing user&#39;s source credentials.\nBefore applying content, the import transaction checks current edit permission,\nnon-authoritative status, source identity, and version ordering. Saved import\noptions are reused without allowing automatic updates to restart a stopped\nsubscription. Resource identity is stored separately from the discovered WebSub\n<code>topic_url<\/code>, so topic migration does not change the semantic resource identifier.<\/p>\n<p>See <code>mod_websub<\/code> for the two-site protocol flow and <code>z_websub_subscription<\/code> for\nrenewal, pending intent, callback rotation, and subscriber state persistence.<\/p>","slug":"websub","is_protected":false,"visible_for":0,"tz":"UTC","language":["en"],"doc_source_hash":"a1ca6a52e14351b5d4661c1633e7cf712928bdf708b829a4f2781db947596038","is_featured":false,"content_group_id":{"id":2551,"is_a":["meta","content_group"],"name":"content_group_imported_docs","title":"Imported documentation","uri":"https:\/\/zotonic.com\/id\/content_group_imported_docs"},"category_id":{"id":322,"is_a":["meta","category"],"name":"model","title":"Models","uri":"https:\/\/test.zotonic.com\/id\/322"},"doc_source_path":"apps\/zotonic_mod_websub\/src\/models\/m_websub.erl","publication_start":"2026-09-29T13:07:54Z","github_url":"https:\/\/github.com\/zotonic\/zotonic\/blob\/master\/apps\/zotonic_mod_websub\/src\/models\/m_websub.erl","pivot_location_lng":null,"doc_source_kind":"model","name":"doc_model_model_websub","is_unfindable":false,"is_published":true,"pivot_geocode":null,"created":"2026-09-29T13:07:54Z","uri":null,"doc_status":"current","is_dependent":false,"erlang_app":"zotonic_mod_websub","publication_end":"9999-06-01T00:00:00Z","modifier_id":{"id":1,"is_a":["person"],"name":"administrator","title":"Site Administrator","uri":"https:\/\/zotonic.com\/id\/1"},"privacy":0,"doc_source_commit":"c34f80c7ab6700e5d77ac5730deaae0e4710e159\n","erlang_module":"m_websub","creator_id":{"id":1,"is_a":["person"],"name":"administrator","title":"Site Administrator","uri":"https:\/\/zotonic.com\/id\/1"},"modified":"2026-10-19T13:18:30Z","title_slug":"websub"},"uri":"https:\/\/zotonic.com\/id\/2712","uri_template":"https:\/\/zotonic.com\/id\/:id","websub":{"hub":"https:\/\/zotonic.com\/.zotonic\/websub","topic":"https:\/\/zotonic.com\/.zotonic\/websub\/topic\/2712"}},"status":"ok"}