subscription.entered event, and when it stops matching you get subscription.exited. Events are pushed to a webhook endpoint, and every event is also in the events feed.
NotEq and NotIn are true. So ["answers.a.p", "NotEq", 0.5] matches documents with no answer yet. Add ["answers.a.p", "Exists", true] to leave those out.
Filters are fixed. To change one, create a new subscription and delete the old. A changed filter would have no honest edges: every document would have to be judged against it again, which is what a new subscription does, and a new name makes that plain.
Create, list and query
eventsdefaults to["entered"]. Addexitedif you keep a list of the matching documents, or you will never hear that one left.endpointdefaults to none: the events go to the events feed only. An endpoint’snamespace_prefixmust cover the namespace, or every namespace of a template.includenames the attributes and answers to send with each event. The attributes and answers the filter reads are always sent.statenever is.bulkissummary(the default) ordeliver. See bulk changes.nameis 1 to 128 letters, digits,_and-. Creating a subscription again with the same name and the same settings returns the existing one, so a retried create is safe.statusissyncinguntil the subscription’s first evaluation in the namespace, thenlive, withlive_at.lag_msis how long the oldest change the subscription hasn’t evaluated has waited. It is accurate only to about a minute: a quiet subscription records its progress about once a minute, solag_msreads 0 until a change has waited longer than that. It tells you the subscription is stuck, not that it is a few seconds behind. It is null while the subscription issyncing.warningslists problems such asjudgment_inactive: a judgment the subscription reads or includes was deactivated or deleted. Its answers stop changing, so the subscription sees nothing new from it. The subscription is never paused or deleted for that.updatechangesevents,include,endpointandbulkfor the changes evaluated after it. It can’t changefilters.deleteremoves the definition, and the next evaluation drops the subscription’s matching set. Events recorded before then, and deliveries already queued, still go out.- The query runs the saved filter with your own
rank_by,top_k,cursorandinclude, and is billed like any query. It takes a namespace, never a template prefix, and its body can’t havefilters. Use it to seed a receiver, and aftersubscription.synced.
What is refused
invalid_request: a filter onfreshness, or onstate; a judgment that doesn’t exist or is computed on read (on_read), in the filter or ininclude;stateininclude; empty or repeatedevents; more than 24 fields, counting those the filter reads; a name the namespace already has or inherits; an endpoint that doesn’t exist or doesn’t cover the namespace; a 101st subscription in a namespace, counting those it inherits; and anupdatethat sendsfilters. See limits.plan_required: a subscription on a template prefix below the Team plan, as for templates.unavailable(503, withRetry-After): a create orupdatethat names an endpoint while the endpoint can’t be checked. Nothing was changed, so retry it.
Events are edges, not states
A subscription sends an event when a document’s match changes, never while it stays the same. As long as every change is live, the events for one document alternate:entered, exited, entered, and so on. A document that joined or left silently, in the first evaluation or in a bulk change, breaks that chain: it can send exited without an entered before it. Those changes are always announced by subscription.synced, and re-reading the members then puts a receiver right.
An entered or exited event carries:
subscription, with itsid,nameanddefined_on, the namespace or template prefix it was created on;namespace, the namespace the document is in;document, with itsid,revision,incarnationandupdated_at;attributesandanswers: those the filter reads and those named ininclude, as a get returns them;cause:changefor a write or a new answer,fanout,periodic, or, for bulk changes you asked to receive,backfill(with the backfill’sjob_id) orresync;sequence, below;url, the document’s get, to fetch the rest with your own key;truncated: a body is at most 64 KB, so past thatattributesandanswersare left out,truncatedistrue, andurlhas them.
exited also has a reason: no_longer_matches, with the current values, which show why the document left; or deleted, with only the document’s id and incarnation. A document created again with the same id is a new life, with a new incarnation, and enters like any new document.
sequence is the number of the evaluation that recorded the event in the namespace. Every event of one evaluation has the same number, and a later evaluation has a higher one, with gaps. For one subscription and document, a higher sequence is newer, so a receiver drops an event older than what it has. A document deleted and created again within one evaluation sends exited and entered with the same sequence; the incarnation tells them apart. The numbers start again when a namespace is deleted and created again, so forget what you kept for a namespace when you delete it.
Only settled answers count
A document is evaluated only once every answer its filter reads is up to date with its current revision. So a write never makes a document drop out on apending answer and come back when the answer lands.
A filter over two judgments waits for both. The
freshness field can’t be used in a subscription’s filter, since only settled answers are seen. Only the answers the filter reads are waited for: an answer named only in include is sent as it stands, and can be pending. See freshness for how this fits with the freshness states.
Events follow answer commits by a few seconds.
Bulk changes
A backfill, or an edit to a threshold your filter reads, can move thousands of documents at once, without anything happening to any of them. So by default,"bulk": "summary", those moves update the subscription without sending an event for each document. You get one subscription.synced event instead:
reason is one of:
created, once the subscription’s first evaluation in the namespace is done;backfill, sent once an evaluation finds no more of the backfill’s answers, or 10 minutes after the first one, whichever comes first. Backfills that overlap share onesynced, with all theirjob_ids;thresholds_changed, when a threshold the filter reads is edited, or a new version changes it;weights_changed, when a composite judgment the filter reads gets new calibration fits.
synced, read the current members again with the subscription’s query.
A subscription whose receiver must see every change sets "bulk": "deliver". It gets an event for every document a backfill or a definition change moves, with cause: "backfill" and the job_id, or cause: "resync". The first evaluation is silent either way.
Answers from fan-outs and periodic runs are live changes, never bulk. When a document has both a live change and a bulk one at the same time, the live change wins.
A new subscription starts from now
A subscription is edge-triggered from when you create it. Its first evaluation in a namespace is silent, whateverbulk says: the documents that already match join without an event. Then one subscription.synced with reason: "created" says how many joined, even when none did, which tells you the subscription is live there. A document written or answered after the subscription was created is the exception: its change sends an event as usual, so the first change after creation is never lost.
Suppose a document matched when you created the subscription and stopped matching before that first evaluation. It sends no exited, and it never sent an entered either. A receiver built from events and the synced re-reads stays consistent. A receiver that listed the matches with a query at creation time does not.
Templates
A subscription created on a template prefix, such asacme/prod/*, applies to every namespace under it, including tenants created later. See templates.
- Per tenant. Each tenant has its own matching set, and its events name the tenant in
namespace.subscription.defined_onnames the prefix the subscription was created on, so a platform routes events without a lookup. - Lazy start. A tenant starts evaluating a template’s subscription at its next change after the subscription exists, so idle tenants cost nothing. Until then the subscription reads
syncingin that tenant. - No opting out. Tenants can’t change, delete or detach a template’s subscription. To leave one out, add a condition on
idor an attribute to the filter. - Names. A tenant can’t create a subscription with the name of one it inherits. A template can create a name a tenant already has; both run, and the tenant’s own is listed first.