Beta

Sockethub 5.0.0-beta.0 is the current active-use release. See the changelog.

Platforms

Supported platforms

Each platform maps ActivityStreams 2.0 onto a specific network. Same JSON envelope on the way in, same shape on the way back. If you need more, the platform interface is small enough to implement in an afternoon.

IRC

Chat and channels on the original open protocol.

@sockethub/platform-irc ↗

Speak to any IRC network — Libera, OFTC, your local. Sockethub handles reconnects, PINGs, and nick collision for you.

The full IRC flow is credentials → connect → join → send. Each envelope shares the same @context; only type and the target change.

1. Credentials

{
  "@context": [
    "https://www.w3.org/ns/activitystreams",
    "https://sockethub.org/ns/context/v1.jsonld",
    "https://sockethub.org/ns/context/platform/irc/v1.jsonld"
  ],
  "type":   "credentials",
  "actor":  {
    "id":   "alice@irc.libera.chat",
    "type": "person",
    "name": "alice"
  },
  "object": {
    "type":   "credentials",
    "nick":   "alice",
    "server": "irc.libera.chat",
    "port":   6697,
    "secure": true,
    "token":  "…"
  }
}

2. Connect

{
  "@context": [ /* as above */ ],
  "type":   "connect",
  "actor":  { "id": "alice@irc.libera.chat", "type": "person", "name": "alice" }
}

3. Join a channel

{
  "@context": [ /* as above */ ],
  "type":   "join",
  "actor":  { "id": "alice@irc.libera.chat",     "type": "person", "name": "alice" },
  "target": { "id": "#sockethub@irc.libera.chat", "type": "room", "name": "#sockethub" }
}

4. Send a message

{
  "@context": [ /* as above */ ],
  "type":   "send",
  "actor":  { "id": "alice@irc.libera.chat",     "type": "person", "name": "alice" },
  "target": { "id": "#sockethub@irc.libera.chat", "type": "room", "name": "#sockethub" },
  "object": { "type": "message", "content": "hey everyone" }
}

XMPP

Federated messaging, rooms, and presence.

@sockethub/platform-xmpp ↗

Connect to any XMPP service — public or self-hosted. Multi-user chat, direct messages, presence, and roster updates all come through as ActivityStreams objects.

Full XMPP MUC flow is credentials → connect → join → send. For a 1:1 message to a contact, skip join and target the person JID directly (shown at the end).

1. Credentials

{
  "@context": [
    "https://www.w3.org/ns/activitystreams",
    "https://sockethub.org/ns/context/v1.jsonld",
    "https://sockethub.org/ns/context/platform/xmpp/v1.jsonld"
  ],
  "type":   "credentials",
  "actor":  {
    "id":   "alice@chat.example",
    "type": "person"
  },
  "object": {
    "type":        "credentials",
    "userAddress": "alice@chat.example",
    "password":    "…",
    "server":      "chat.example",
    "resource":    "sockethub"
  }
}

2. Connect

{
  "@context": [ /* as above */ ],
  "type":   "connect",
  "actor":  { "id": "alice@chat.example", "type": "person" }
}

3. Join a room (MUC)

{
  "@context": [ /* as above */ ],
  "type":   "join",
  "actor":  { "id": "alice@chat.example",              "type": "person" },
  "target": { "id": "sockethub@conference.chat.example", "type": "room" }
}

4a. Send to the room

{
  "@context": [ /* as above */ ],
  "type":   "send",
  "actor":  { "id": "alice@chat.example",              "type": "person" },
  "target": { "id": "sockethub@conference.chat.example", "type": "room" },
  "object": { "type": "message", "content": "hey everyone" }
}

4b. Send 1:1 to a contact (no join needed)

{
  "@context": [ /* as above */ ],
  "type":   "send",
  "actor":  { "id": "alice@chat.example", "type": "person" },
  "target": { "id": "bob@chat.example",   "type": "person" },
  "object": { "type": "message", "content": "ping" }
}

RSS / Atom

Fetch, parse, and stream updates from feeds.

@sockethub/platform-feeds ↗

Point the feeds platform at any RSS or Atom URL and it returns normalized entries as ActivityStreams objects. Useful for reader apps, dashboards, or anywhere you'd otherwise reach for a polling cron.

Fetch once

{
  "@context": [
    "https://www.w3.org/ns/activitystreams",
    "https://sockethub.org/ns/context/v1.jsonld",
    "https://sockethub.org/ns/context/platform/feeds/v1.jsonld"
  ],
  "type":   "fetch",
  "actor":  { "id": "https://example.org/reader",    "type": "person" },
  "target": { "id": "https://blog.example/feed.xml", "type": "feed" }
}

Metadata

Link previews and structured-data extraction.

@sockethub/platform-metadata ↗ New in 5.0

Fetch a URL, extract Open Graph, oEmbed, microformats2, and JSON-LD, and hand back a single normalized object. Built for chat link previews and reader summaries.

Fetch metadata for a URL

{
  "@context": [
    "https://www.w3.org/ns/activitystreams",
    "https://sockethub.org/ns/context/v1.jsonld",
    "https://sockethub.org/ns/context/platform/metadata/v1.jsonld"
  ],
  "type":   "fetch",
  "actor":  { "id": "https://example.org/reader",    "type": "person" },
  "target": { "id": "https://blog.example/article", "type": "link" }
}

CalDAV

Calendars, events, and tasks on any CalDAV server.

@sockethub/platform-caldav ↗ New in 5.0

Discover calendars, then read, create, update, and delete events (VEVENT) and tasks (VTODO) on any CalDAV server — Nextcloud, Radicale, Fastmail, and friends. Every write is guarded by an ETag check, so you never silently overwrite a change made by another client.

The full CalDAV flow is credentials → fetch → query / create / update / delete. fetch discovers your calendars; every later request targets one of the discovered calendar URLs.

1. Credentials

{
  "@context": [
    "https://www.w3.org/ns/activitystreams",
    "https://sockethub.org/ns/context/v1.jsonld",
    "https://sockethub.org/ns/context/platform/caldav/v1.jsonld"
  ],
  "type":   "credentials",
  "actor":  {
    "id":   "caldav:alice",
    "type": "person"
  },
  "object": {
    "type":  "credentials",
    "url":   "https://calendar.example/dav/",
    "token": "…"
  }
}

token is sent as an HTTP Bearer token (your app runs the OAuth flow itself). For Basic auth — typically an app password — replace it with username and password. The URL can be a service root; discovery also tries /.well-known/caldav.

2. Discover calendars

{
  "@context": [ /* as above */ ],
  "type":   "fetch",
  "actor":  { "id": "caldav:alice", "type": "person" }
}

The response lists your calendars as objects with an id (the calendar URL), a name, a color, and which components they support (event, task).

3. Query events in a date range

{
  "@context": [ /* as above */ ],
  "type":   "query",
  "actor":  { "id": "caldav:alice", "type": "person" },
  "target": {
    "id":   "https://calendar.example/calendars/alice/personal/",
    "type": "calendar"
  },
  "object": {
    "type":      "event",
    "startTime": "2026-08-01T00:00:00Z",
    "endTime":   "2026-09-01T00:00:00Z"
  }
}

Each returned item carries its resource id, uid, and etag — keep the ETag for later update or delete requests.

4. Create an event

{
  "@context": [ /* as above */ ],
  "type":   "create",
  "actor":  { "id": "caldav:alice", "type": "person" },
  "target": {
    "id":   "https://calendar.example/calendars/alice/personal/",
    "type": "calendar"
  },
  "object": {
    "type":      "event",
    "name":      "Project planning",
    "startTime": "2026-08-03T15:00:00",
    "endTime":   "2026-08-03T16:00:00",
    "timeZone":  "Europe/Prague"
  }
}

Events and tasks support recurrence, attendees, reminders, and attachments. update and delete send the item's etag; if another client changed it in the meantime you get caldav:conflict back — query again and retry with the fresh ETag.

CardDAV

Contacts and address books on any CardDAV server.

@sockethub/platform-carddav ↗ New in 5.0

Discover address books, then search, create, update, and delete vCard contacts on any CardDAV server — Nextcloud, Radicale, Fastmail, and friends. Contacts come back as plain ActivityStreams person objects, and every write is guarded by an ETag check, so you never silently overwrite a change made by another client.

The full CardDAV flow is credentials → fetch → query / create / update / delete. fetch discovers your address books; every later request targets one of the discovered address book URLs.

1. Credentials

{
  "@context": [
    "https://www.w3.org/ns/activitystreams",
    "https://sockethub.org/ns/context/v1.jsonld",
    "https://sockethub.org/ns/context/platform/carddav/v1.jsonld"
  ],
  "type":   "credentials",
  "actor":  {
    "id":   "carddav:alice",
    "type": "person"
  },
  "object": {
    "type":     "credentials",
    "url":      "https://contacts.example/dav/",
    "username": "alice",
    "password": "…"
  }
}

username and password — typically an app password — are sent as Basic or Digest auth. For Bearer auth (your app runs the OAuth flow itself) replace them with a single token. The URL can be a service root; discovery also tries /.well-known/carddav.

2. Discover address books

{
  "@context": [ /* as above */ ],
  "type":   "fetch",
  "actor":  { "id": "carddav:alice", "type": "person" }
}

The response lists your address books as objects with an id (the address book URL) and a name.

3. Search contacts

{
  "@context": [ /* as above */ ],
  "type":   "query",
  "actor":  { "id": "carddav:alice", "type": "person" },
  "target": {
    "id":   "https://contacts.example/addressbooks/alice/personal/",
    "type": "addressBook"
  },
  "object": {
    "type":   "contactQuery",
    "text":   "Bob",
    "fields": [ "name", "email" ],
    "limit":  50
  }
}

Leave text out to list the whole address book. Each returned contact is a person with its resource id, uid, and etag, plus structured emails, telephones, addresses, and photoUrls — keep the ETag for later update or delete requests.

4. Create a contact

{
  "@context": [ /* as above */ ],
  "type":   "create",
  "actor":  { "id": "carddav:alice", "type": "person" },
  "target": {
    "id":   "https://contacts.example/addressbooks/alice/personal/",
    "type": "addressBook"
  },
  "object": {
    "type":       "person",
    "name":       "Bob Example",
    "givenName":  "Bob",
    "familyName": "Example",
    "emails": [
      { "value": "bob@example.com", "types": [ "work" ], "preferred": true }
    ]
  }
}

The platform reads vCard 3.0 and 4.0 and writes vCard 4.0. update sends the complete contact back with its etag; properties your app doesn't understand are preserved rather than erased. If another client changed the card in the meantime you get carddav:conflict back — query again and retry with the fresh ETag.

Want to add another protocol? Platform interface Get started