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.
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.
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" }
}
Receive over IMAP, send over SMTP.
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.
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.
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.
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.
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.
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.
Outgoing text messages from a professional account.
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.
Outgoing text messages through the WhatsApp Business Cloud API.
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