---
title: NestJS
description: Provide the Social SDK client through NestJS dependency injection, publish from services, and verify webhooks with a raw-body controller.
---

In NestJS, Social SDK becomes an injectable provider: one client instance constructed from configuration, shared across services, with a controller that keeps webhook bodies raw for verification.

## Install

```package-install
@opencoredev/social-sdk
```

## Provide the client

```ts src/social/social.module.ts
import { Module } from "@nestjs/common";
import { ConfigModule, ConfigService } from "@nestjs/config";
import { createSocial } from "@opencoredev/social-sdk";
import { x } from "@opencoredev/social-sdk/x";

export const SOCIAL = Symbol("SOCIAL");

@Module({
  imports: [ConfigModule],
  providers: [
    {
      provide: SOCIAL,
      inject: [ConfigService],
      useFactory: (config: ConfigService) =>
        createSocial({
          backend: x({
            auth: {
              userId: config.getOrThrow("X_USER_ID"),
              accessToken: config.getOrThrow("X_ACCESS_TOKEN"),
            },
          }),
        }),
    },
  ],
  exports: [SOCIAL],
})
export class SocialModule {}
```

## Publish from a service

```ts src/publishing/publishing.service.ts
import { Inject, Injectable } from "@nestjs/common";
import { connectedAccountRef } from "@opencoredev/social-sdk";
import { SOCIAL } from "../social/social.module";

@Injectable()
export class PublishingService {
  constructor(@Inject(SOCIAL) private readonly social: ReturnType<typeof createSocial>) {}

  async publish(tenantId: string, draftId: string, text: string) {
    const accountId = await this.resolveAuthorizedAccount(tenantId); // your lookup

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

    await this.saveOutcomes(tenantId, draftId, result.outcomes); // your storage
    return result;
  }
}
```

Guards and your tenant lookup own authorization. The SDK checks capabilities and account-to-backend binding, not your application's user model.

## Webhooks with a raw body

Enable Nest's raw-body support and read `req.rawBody` in the events controller. `req.rawBody` is a `Buffer`, which the verifier accepts as a `Uint8Array`. Convert Node's header record to a web `Headers` object:

```ts src/main.ts
const app = await NestFactory.create(AppModule, { rawBody: true });
```

```ts src/events/events.controller.ts
import { Controller, HttpCode, Post, Req, UnauthorizedException } from "@nestjs/common";
import type { RawBodyRequest } from "@nestjs/common";
import type { Request } from "express";
import { SocialError } from "@opencoredev/social-sdk";
import { verifyZernioWebhook } from "@opencoredev/social-sdk/server";

@Controller("events")
export class EventsController {
  @Post()
  @HttpCode(202)
  async receive(@Req() req: RawBodyRequest<Request>) {
    try {
      await verifyZernioWebhook({
        secret: process.env.ZERNIO_WEBHOOK_SECRET!,
        headers: new Headers(req.headers as Record<string, string>),
        body: req.rawBody ?? new Uint8Array(),
      });
    } catch (error) {
      if (error instanceof SocialError && error.code === "unauthorized") {
        throw new UnauthorizedException();
      }
      throw error;
    }

    // decode, resolve tenants, accept into your durable inbox
    return "accepted";
  }
}
```

Nest fills `req.rawBody` only for bodies its built-in JSON and URL-encoded parsers handle, so providers must send `Content-Type: application/json`.

The [events guide](/events) covers decoding, tenant quarantine, and the durable inbox worker.

## Notes

- Run on Node 22.12+ or Node 24; the SDK is ESM-only, so use a NestJS setup that emits or runs ESM.
- One provider instance per process keeps per-backend concurrency limits meaningful.
- Put scheduled publishing in a durable job runner like [Trigger.dev](/integrations/trigger-dev) or [Inngest](/integrations/inngest) rather than `@nestjs/schedule` timers that vanish on redeploys, unless a missed run is acceptable.
