Permissioned Spaces

Write Notifications

Write notifications let external services receive webhooks when records change in a space. A service registers an endpoint, and HappyView pushes notifications to it when records are created, updated, or deleted — or when the space itself is deleted.

Registrations expire after 24 hours and must be renewed.

Registering for notifications

Requires DPoP auth or a space credential. The caller provides the DID of the service that will receive notifications and the HTTPS endpoint to deliver them to.

const response = await fetch("https://happyview.example.com/xrpc/com.atproto.space.registerNotify", {
  method: "POST",
  headers: {
    "X-Client-Key": CLIENT_KEY,
    "Authorization": `DPoP ${ACCESS_TOKEN}`,
    "DPoP": DPOP_PROOF,
    "Content-Type": "application/json",
  },
  body: JSON.stringify({
    space: "ats://did:plc:abc123/com.example.forum/main",
    serviceDid: "did:web:feed.example.com",
    endpoint: "https://feed.example.com/webhooks/space-writes",
  }),
});
interface RegisterNotifyResponse {
  id: string;
}
const data: RegisterNotifyResponse = await response.json();

Input:

FieldTypeRequiredDescription
spacestringYesSpace URI (ats://...)
serviceDidstringYesDID of the service receiving notifications
endpointstringYesHTTPS endpoint to deliver notifications to

Response (200):

{
  "id": "550e8400-e29b-41d4-a716-446655440000"
}

Write notification payload

When a record is created, updated, or deleted in a space, HappyView POSTs a JSON payload to each registered endpoint:

{
  "space": "space-id",
  "did": "did:plc:author",
  "collection": "com.example.forum.post",
  "rkey": "3jwq5dya2gy2z",
  "cid": "bafyreie5cvv4h45feadgeuwhbcutmh6t7ceseocckahdoe6uat64zmz454"
}
FieldTypeDescription
spacestringInternal space ID
didstringDID of the author who made the change
collectionstring (NSID)Collection the record belongs to
rkeystringRecord key
cidstring?CID of the new record value (null for deletes)

Notifications are delivered to both per-author registrations (matching serviceDid) and space-wide registrations (no author filter). Delivery is best-effort — if the endpoint is unreachable, the notification is dropped.

Pushing a write notification

Server-to-server endpoint. Triggers write notifications to all registered endpoints for a space. This is used internally by HappyView when records change, but can also be called externally.

const response = await fetch("https://happyview.example.com/xrpc/com.atproto.space.notifyWrite", {
  method: "POST",
  headers: {
    "Content-Type": "application/json",
  },
  body: JSON.stringify({
    space: "ats://did:plc:abc123/com.example.forum/main",
    did: "did:plc:author456",
    collection: "com.example.forum.post",
    rkey: "3jwq5dya2gy2z",
    cid: "bafyreie5cvv4h45feadgeuwhbcutmh6t7ceseocckahdoe6uat64zmz454",
  }),
});
const data = await response.json();
// { "success": true }

Input:

FieldTypeRequiredDescription
spacestringYesSpace URI (ats://...)
didstringYesDID of the author who made the change
collectionstring (NSID)YesCollection the record belongs to
rkeystringYesRecord key
cidstringNoCID of the record (omit for deletes)

Response (200):

{
  "success": true
}

Notifying space deletion

Server-to-server endpoint. Notifies all registered endpoints that a space has been deleted. Registered endpoints receive { "space": "<space-id>" }.

const response = await fetch("https://happyview.example.com/xrpc/com.atproto.space.notifySpaceDeleted", {
  method: "POST",
  headers: {
    "Content-Type": "application/json",
  },
  body: JSON.stringify({
    space: "ats://did:plc:abc123/com.example.forum/main",
  }),
});
const data = await response.json();
// { "success": true }

Input:

FieldTypeRequiredDescription
spacestringYesSpace URI (ats://...)

Response (200):

{
  "success": true
}