Skip to content
Social SDK megaphone markSocial SDK
Esc
↑↓navigate↵open⌘Jpreview
On this page

Hono

Publish to social platforms from Hono routes on Node or Bun using web-standard Request and Response with Social SDK.

Hono’s fetch-native design fits Social SDK directly: routes receive a standard Request, webhook verification reads its raw bytes, and the same app runs on Node 22.12+ or Bun without adapters.

Install

npm install @opencoredev/social-sdk
pnpm add @opencoredev/social-sdk
yarn add @opencoredev/social-sdk
bun add @opencoredev/social-sdk
nub add @opencoredev/social-sdk
aube add @opencoredev/social-sdk

Publish from a route

import { Hono } from "hono";
import { createSocial, connectedAccountRef } from "@opencoredev/social-sdk";
import { bluesky } from "@opencoredev/social-sdk/bluesky";

const social = createSocial({
  backend: bluesky({
    auth: {
      service: process.env.BLUESKY_SERVICE!,
      did: process.env.BLUESKY_DID!,
      accessJwt: process.env.BLUESKY_ACCESS_JWT!,
    },
  }),
});

const app = new Hono();

app.post("/publish", async (c) => {
  const tenant = await requireTenant(c); // your auth middleware
  const { text, draftId } = await c.req.json();

  const result = await social.posts.publish({
    targets: [
      {
        account: connectedAccountRef({
          backend: "default",
          platform: "bluesky",
          accountId: await resolveAuthorizedAccount(tenant),
        }),
      },
    ],
    content: { text },
    idempotencyKey: draftId,
  });

  return c.json({
    status: result.status,
    outcomes: result.outcomes.map((o) => ({ state: o.state })),
  });
});

export default app;

Webhooks from the raw request

Hono exposes the untouched request as c.req.raw. Read its bytes before any JSON parsing and pass its Headers object as is:

import { SocialError } from "@opencoredev/social-sdk";
import { verifyZernioWebhook } from "@opencoredev/social-sdk/server";

app.post("/events", async (c) => {
  const body = new Uint8Array(await c.req.raw.arrayBuffer());

  try {
    await verifyZernioWebhook({
      secret: process.env.ZERNIO_WEBHOOK_SECRET!,
      headers: c.req.raw.headers,
      body,
    });
  } catch (error) {
    if (error instanceof SocialError && error.code === "unauthorized") {
      return c.text("unauthorized", 401);
    }
    throw error;
  }

  // decode, resolve tenants, accept into your durable inbox
  return c.text("accepted", 202);
});

See the events guide for the decode, quarantine, and worker steps.

Mount the example handler

The repository’s example handler speaks Request/Response, so it drops into Hono with one adapter line:

import { Hono } from "hono";
import { createExampleHandler } from "@opencoredev/social-sdk-example/app";
import { createHonoFetch } from "@opencoredev/social-sdk-example/frameworks/hono";

const example = createHonoFetch(createExampleHandler());
const app = new Hono();
app.all("/social/*", (c) => example(c.req.raw));

Framework recipes documents what the example handler provides.

Run on Node or Bun

// Bun
import app from "./app.js";
Bun.serve({ port: 3030, fetch: app.fetch });
// Node
import { serve } from "@hono/node-server";
import app from "./app.js";
serve({ fetch: app.fetch, port: 3030 });

Notes

  • Both runtimes are tested SDK targets; the SDK is ESM-only.
  • Cloudflare Workers is not a tested SDK runtime. For Workers-fronted apps, run the SDK in a Node or Bun service behind the Worker.
  • Keep one client instance per process so per-backend concurrency limits apply.

Last updated on September 23, 2026