Beta

Sockethub 5.0.0-beta.7 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" }
}

Email

Receive over IMAP, send over SMTP.

@sockethub/platform-email ↗ New in 5.0

Read a mailbox and send mail from the browser. The mail server stays the message store: Sockethub keeps no mailbox database and never changes read flags, moves messages, or deletes mail. Each client session gets its own IMAP connection, so two clients on the same account can watch different folders.

The full email flow is credentials → connect → fetch / watch / send. Configure IMAP, SMTP, or both; each needs a username and exactly one of password or token.

1. Credentials

{
  "@context": [
    "https://www.w3.org/ns/activitystreams",
    "https://sockethub.org/ns/context/v1.jsonld",
    "https://sockethub.org/ns/context/platform/email/v1.jsonld"
  ],
  "type":   "credentials",
  "actor":  { "id": "me@example.com", "type": "person", "name": "Alice Example" },
  "object": {
    "type": "credentials",
    "from": "me@example.com",
    "imap": { "host": "imap.example.com", "port": 993, "secure": true,
              "username": "me@example.com", "password": "…" },
    "smtp": { "host": "smtp.example.com", "port": 587, "secure": false,
              "username": "me@example.com", "token": "…" }
  }
}

secure: true uses TLS immediately; false requires STARTTLS. Plaintext credentials are never accepted. token is an access token your app obtained itself; Sockethub runs no OAuth flow and does not refresh tokens.

2. Connect

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

3. Fetch a page of messages

{
  "@context": [ /* as above */ ],
  "type":   "fetch",
  "actor":  { "id": "me@example.com", "type": "person" },
  "object": {
    "folders": [ "INBOX" ],
    "from":    [ "friend@example.com" ],
    "limit":   20
  }
}

The reply is a collection of post activities (sender as actor, a message as object) plus per-folder cursors and hasMore. Send the cursors back in the next fetch to get the next page. Attachments are listed by filename and type; their contents are never returned.

4. Watch a folder for new mail

{
  "@context": [ /* as above */ ],
  "type":   "watch",
  "actor":  { "id": "me@example.com", "type": "person" },
  "object": { "folder": "INBOX" }
}

New messages arrive on your message listener as post activities, using IMAP IDLE where the server supports it. One watch per session; unwatch stops it.

5. Send

{
  "@context": [ /* as above */ ],
  "type":   "send",
  "actor":  { "id": "me@example.com", "type": "person", "name": "Alice Example" },
  "object": {
    "to":      [ "friend@example.com" ],
    "subject": "Hello",
    "content": "Hello from Sockethub!"
  }
}

The acknowledgement carries a submission with the server's messageId and the accepted and rejected recipients. Acceptance means the SMTP server took the message, not that it was delivered. No Sent-folder copy is saved.

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://blog.example/feed.xml", "type": "feed" }
}

The feed URL is the actor; there is no target. The reply is a collection whose items are post activities, one per entry. Add an object with since (RFC 3339) and limit to fetch only what's new:

{
  "@context": [ /* as above */ ],
  "type":   "fetch",
  "actor":  { "id": "https://blog.example/feed.xml", "type": "feed" },
  "object": { "since": "2026-11-01T00:00:00Z", "limit": 10 }
}

Metadata

Link previews and structured-data extraction.

@sockethub/platform-metadata ↗ New in 5.0

Fetch a URL, extract Open Graph tags, the canonical URL, favicon, images, and any schema.org Event published as JSON-LD, and hand back a single normalized page object. Built for chat link previews, reader summaries, and event cards.

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://blog.example/article", "type": "website" }
}

The page URL is the actor; there is no target. The reply's object carries title, description, image (an array, possibly empty), url, favicon, and — when the page declares one — an event with its name, start, end, location, and status.

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.

Messenger

Outgoing text messages as a Facebook Page.

@sockethub/platform-messenger ↗ New in 5.0

Reply to people who message your Facebook Page, through Meta's Messenger API. Sockethub sends as the Page, not as a personal profile. Incoming messages and recipient discovery stay with your own webhook integration; this platform covers the reply.

The full Messenger flow is credentials → send. The platform is stateless: there is no connect step and no webhook to host.

1. Credentials

{
  "@context": [
    "https://www.w3.org/ns/activitystreams",
    "https://sockethub.org/ns/context/v1.jsonld",
    "https://sockethub.org/ns/context/platform/messenger/v1.jsonld"
  ],
  "type":   "credentials",
  "actor":  { "id": "<account id>", "type": "person" },
  "object": {
    "type":       "credentials",
    "token":      "…",
    "apiVersion": "v22.0"
  }
}

actor.id is your Facebook Page ID and token is a Page access token with pages_messaging, obtained outside Sockethub. Choose a Graph API version your Meta app supports; it is never guessed.

2. Send a text message

{
  "@context": [ /* as above */ ],
  "type":   "send",
  "actor":  { "id": "<account id>",   "type": "person" },
  "target": { "id": "<recipient id>", "type": "person" },
  "object": { "type": "message", "content": "Hello from Sockethub!" }
}

target.id is the recipient's Page-scoped ID (PSID) from your incoming-message integration. Text only, up to 2000 characters. The acknowledgement returns your activity with object.id set to Meta's message ID, which confirms acceptance by the API, not delivery. A closed messaging window comes back as the stable error marker meta:messaging-window-closed. Set object.humanAgent: true only for a manual support reply when your app has Human Agent access.

Instagram

Outgoing text messages from a professional account.

@sockethub/platform-instagram ↗ New in 5.0

Reply to people who message your Instagram professional account, through Meta's Instagram API. Incoming messages and recipient discovery stay with your own webhook integration; this platform covers the reply.

The full Instagram flow is credentials → send. The platform is stateless: there is no connect step and no webhook to host.

1. Credentials

{
  "@context": [
    "https://www.w3.org/ns/activitystreams",
    "https://sockethub.org/ns/context/v1.jsonld",
    "https://sockethub.org/ns/context/platform/instagram/v1.jsonld"
  ],
  "type":   "credentials",
  "actor":  { "id": "<account id>", "type": "person" },
  "object": {
    "type":       "credentials",
    "token":      "…",
    "apiVersion": "v22.0"
  }
}

actor.id is your Instagram professional account ID and token is an Instagram Login access token with instagram_business_manage_messages. For Facebook Login, add login: "facebook" and pageId with a Page token that has instagram_manage_messages; the login method is explicit and never inferred from the token.

2. Send a text message

{
  "@context": [ /* as above */ ],
  "type":   "send",
  "actor":  { "id": "<account id>",   "type": "person" },
  "target": { "id": "<recipient id>", "type": "person" },
  "object": { "type": "message", "content": "Hello from Sockethub!" }
}

target.id is the recipient's Instagram-scoped ID (IGSID) from your incoming-message integration. Text only, up to 1000 bytes of UTF-8. The acknowledgement returns your activity with object.id set to Meta's message ID, which confirms acceptance by the API, not delivery. A closed messaging window comes back as the stable error marker meta:messaging-window-closed. Set object.humanAgent: true only for a manual support reply when your app has Human Agent access.

WhatsApp

Outgoing text messages through the WhatsApp Business Cloud API.

@sockethub/platform-whatsapp ↗ New in 5.0

Send text messages from a WhatsApp Business phone number using Meta's Cloud API. The platform is stateless: set credentials, then send. Incoming messages and recipient discovery stay in your own Meta webhook integration.

The full WhatsApp flow is credentials → send. Sockethub does not host webhooks or run OAuth; your app obtains and supplies the access token.

1. Credentials

{
  "@context": [
    "https://www.w3.org/ns/activitystreams",
    "https://sockethub.org/ns/context/v1.jsonld",
    "https://sockethub.org/ns/context/platform/whatsapp/v1.jsonld"
  ],
  "type":   "credentials",
  "actor":  { "id": "<phone number id>", "type": "person" },
  "object": {
    "type":       "credentials",
    "token":      "…",
    "apiVersion": "v22.0"
  }
}

Create a Meta developer app with the WhatsApp use case and register a business phone number. actor.id is its phone number ID from Meta, and the access token needs whatsapp_business_messaging. Choose an API version supported by your Meta app.

2. Send a text message

{
  "@context": [ /* as above */ ],
  "type":   "send",
  "actor":  { "id": "<phone number id>", "type": "person" },
  "target": { "id": "+15551234567",         "type": "person" },
  "object": { "type": "message", "content": "Hello from Sockethub!" }
}

Use the recipient's international phone number. The platform adds a leading plus sign when one is missing. Messages are text only, up to 4096 characters. Meta accepts free-form text only within 24 hours of the recipient's last message; template messages are not supported. A successful acknowledgement includes Meta's message ID and confirms API acceptance, not delivery. Incoming messages, receipts, media, and webhook hosting are outside this platform.

Want to add another protocol? Platform interface Get started