Oi
OiChat
← 문서 목록
Server Mode

Server Mode 연동 가이드

테넌트 서버에서 OiChat API를 호출하여 채팅 기능을 제어하는 방법을 안내합니다.

Server Mode 아키텍처

┌──────────┐     ┌──────────────┐     ┌───────────┐
│ 모바일 앱 │────>│ 테넌트 서버   │────>│ OiChat API│
│(SDK 연동) │<────│ (여러분 서버) │<────│           │
└──────────┘     └──────────────┘     └───────────┘
     │                                       │
     └───────── WebSocket (실시간) ───────────┘

테넌트 서버 역할:
  1. JWT 토큰 발급 → 모바일 앱에 전달
  2. 채팅방 생성/삭제 → 비즈니스 로직에 따라
  3. Webhook 수신 → 푸시 알림 커스터마이즈

1API 인증 방식

테넌트 서버에서 OiChat API를 호출할 때 API Key Secret Key를 헤더에 포함합니다. 별도 서버 등록은 필요 없습니다.

API Key와 Secret Key는 대시보드 > 프로젝트 상세에서 확인할 수 있습니다.
http
POST https://api.oichatapi.com/api/v1/auth/token
Headers:
  x-api-key: ak_live_xxxxxxxxxxxx
  x-secret-key: sk_xxxxxxxxxxxx
  Content-Type: application/json

2JWT 토큰 발급

모바일 사용자가 로그인하면, 테넌트 서버가 OiChat에서 JWT를 발급받아 앱에 전달합니다. 이 JWT로 모바일 앱이 WebSocket에 연결됩니다.

Node.js (Express)
const OICHAT_API = 'https://api.oichatapi.com';
const API_KEY = process.env.OICHAT_API_KEY;
const SECRET_KEY = process.env.OICHAT_SECRET_KEY;

// OiChat API 공통 호출 함수
async function oichatApi(method, path, body) {
  const res = await fetch(`${OICHAT_API}${path}`, {
    method,
    headers: {
      'Content-Type': 'application/json',
      'x-api-key': API_KEY,
      'x-secret-key': SECRET_KEY,
    },
    body: body ? JSON.stringify(body) : undefined,
  });
  if (!res.ok) throw new Error(await res.text());
  return res.json();
}

// 사용자 로그인 시 OiChat JWT 발급
app.post('/api/chat/token', authMiddleware, async (req, res) => {
  const userId = req.user.id;  // 자사 회원 ID 그대로 사용

  const { token } = await oichatApi('POST', '/api/v1/auth/token', {
    user_id: userId,
    user_name: req.user.name,    // 선택: 표시 이름
  });

  res.json({ token });
});
Spring Boot
@RestController
@RequestMapping("/api/chat")
public class ChatController {

    @Value("${oichat.api-url}") String apiUrl;
    @Value("${oichat.api-key}") String apiKey;
    @Value("${oichat.secret-key}") String secretKey;

    private final RestClient restClient = RestClient.create();

    @PostMapping("/token")
    public Map<String, String> getToken(Authentication auth) {
        String userId = auth.getName();

        var response = restClient.post()
            .uri(apiUrl + "/api/v1/auth/token")
            .header("x-api-key", apiKey)
            .header("x-secret-key", secretKey)
            .body(Map.of("user_id", userId))
            .retrieve()
            .body(Map.class);

        return Map.of("token", (String) response.get("token"));
    }
}

3채팅방 관리

비즈니스 로직에 따라 테넌트 서버에서 채팅방을 생성/삭제합니다. 모바일 앱 사용자에게는 실시간으로 알림이 전달됩니다.

Node.js — 채팅방 생성
// 1:1 채팅방 생성 (예: 주문 완료 시 판매자-구매자 연결)
app.post('/api/orders/:id/chat', authMiddleware, async (req, res) => {
  const order = await Order.findById(req.params.id);

  const room = await oichatApi('POST', '/api/v1/rooms', {
    room_id: `order_${order.id}`,          // 고유 ID (중복 방지)
    name: `주문 #${order.orderNumber}`,
    type: 'direct',
    participants: [order.buyerId, order.sellerId],
    metadata: {
      order_id: order.id,
      product_name: order.productName,
    },
  });

  res.json({ roomId: room.room_id });
  // → 구매자/판매자 앱에 실시간 room.created 이벤트 전달됨
});

사용 가능한 채팅방 API

MethodPath설명
POST/api/v1/rooms채팅방 생성
GET/api/v1/rooms/:room_id채팅방 조회
PATCH/api/v1/rooms/:room_id채팅방 수정
DELETE/api/v1/rooms/:room_id채팅방 삭제
POST/api/v1/rooms/:room_id/participants참여자 추가
DELETE/api/v1/rooms/:room_id/participants/:user_id참여자 제거

4메시지 API

서버에서 직접 메시지를 보내거나, 메시지 이력을 조회할 수 있습니다. 시스템 알림, 자동 응답 봇 등에 활용됩니다.

서버에서 시스템 메시지 전송
// 주문 상태 변경 시 채팅방에 시스템 메시지 전송
async function notifyOrderStatus(roomId, status) {
  await oichatApi('POST', '/api/v1/messages', {
    room_id: roomId,
    sender_id: 'system',
    content: `주문 상태가 "${status}"(으)로 변경되었습니다.`,
    metadata: { type: 'system_notification' },
  });
}
MethodPath설명
POST/api/v1/messages메시지 전송
GET/api/v1/rooms/:room_id/messages메시지 목록 조회
PATCH/api/v1/messages/:id메시지 수정
DELETE/api/v1/messages/:id메시지 삭제
POST/api/v1/rooms/:room_id/messages/read읽음 처리
GET/api/v1/rooms/:room_id/unread안 읽은 수 조회

5Webhook 수신

오프라인 사용자에게 푸시 알림을 보내야 할 때, OiChat이 테넌트 서버로 Webhook을 전송합니다. 자체 푸시 로직을 커스터마이즈할 수 있습니다.

Webhook은 대시보드 > 프로젝트 설정에서 URL 등록 후, Push Mode를 "Webhook"으로 설정하면 활성화됩니다.
Node.js — Webhook 수신 엔드포인트
const crypto = require('crypto');

// Webhook 서명 검증
function verifySignature(secret, timestamp, body, signature) {
  const expected = crypto
    .createHmac('sha256', secret)
    .update(`${timestamp}.${body}`)
    .digest('hex');
  return crypto.timingSafeEqual(
    Buffer.from(signature), Buffer.from(expected)
  );
}

app.post('/webhook/oichat', express.raw({ type: '*/*' }), (req, res) => {
  const signature = req.headers['x-oichat-signature'];
  const timestamp = req.headers['x-oichat-timestamp'];
  const body = req.body.toString();

  // 1. 서명 검증
  if (!verifySignature(WEBHOOK_SECRET, timestamp, body, signature)) {
    return res.status(401).send('Invalid signature');
  }

  // 2. 5분 이상 된 요청 거부 (Replay Attack 방지)
  if (Date.now() - Number(timestamp) > 5 * 60 * 1000) {
    return res.status(401).send('Expired');
  }

  const payload = JSON.parse(body);

  // 3. 이벤트 처리
  switch (payload.event) {
    case 'push.notification':
      const { offline_user_ids, notification } = payload.data;
      // 자체 푸시 발송 (FCM, APNs 등)
      sendMyPush(offline_user_ids, notification);
      break;
  }

  res.status(200).send('ok');
});

Webhook Payload 형식

json
{
  "event": "push.notification",
  "project_id": "proj_abc123",
  "data": {
    "room_id": "order_789",
    "sender_id": "user_456",
    "offline_user_ids": ["user_100", "user_200"],
    "notification": {
      "title": "새 메시지",
      "body": "안녕하세요! 주문 관련 문의드립니다."
    }
  },
  "timestamp": "1712160000000"
}

보안 헤더

헤더설명
x-oichat-signatureHMAC-SHA256 서명 (Secret Key 기반)
x-oichat-timestamp요청 생성 시각 (ms)

6파일 업로드

Presigned URL을 발급받아 S3 호환 스토리지에 직접 업로드합니다. 서버를 거치지 않으므로 대용량 파일도 빠르게 처리됩니다.

Presigned URL 발급 → 업로드
// 1. Presigned URL 발급
const { upload_url, file_url } = await oichatApi(
  'POST', '/api/v1/upload/presigned-url', {
    file_name: 'photo.jpg',
    content_type: 'image/jpeg',
  }
);

// 2. 파일 직접 업로드 (S3)
await fetch(upload_url, {
  method: 'PUT',
  headers: { 'Content-Type': 'image/jpeg' },
  body: fileBuffer,
});

// 3. 메시지에 파일 URL 포함하여 전송
await oichatApi('POST', '/api/v1/messages', {
  room_id: roomId,
  sender_id: userId,
  content: file_url,
  content_type: 'image',
});

7SDK 연동 (클라이언트 측)

모바일/웹 앱에서 SDK를 초기화할 때 tokenProvider를 등록하면, 토큰 만료 시 자동으로 테넌트 서버에 재발급을 요청합니다.

Flutter SDK
final oichat = OiChat(OiChatConfig(
  apiUrl: 'https://api.oichatapi.com',
  wsUrl: 'wss://ws.oichatapi.com/connection/websocket',
  // 토큰 만료 시 자동 호출
  tokenProvider: () async {
    final res = await http.post(
      Uri.parse('https://your-server.com/api/chat/token'),
      headers: {'Authorization': 'Bearer ${userToken}'},
    );
    return jsonDecode(res.body)['token'];
  },
));

await oichat.connect(userId: 'user_123');
React SDK
const oichat = new OiChat({
  apiUrl: 'https://api.oichatapi.com',
  wsUrl: 'wss://ws.oichatapi.com/connection/websocket',
  tokenProvider: async () => {
    const res = await fetch('/api/chat/token', {
      headers: { Authorization: `Bearer ${userToken}` },
    });
    const data = await res.json();
    return data.token;
  },
});

await oichat.connect({ userId: 'user_123' });

전체 흐름 요약

1. 테넌트 서버: OiChat에서 JWT 발급
   POST /api/v1/auth/token { user_id }
        ↓
2. 모바일 앱: JWT로 WebSocket 연결
   SDK.connect(token)
        ↓
3. 테넌트 서버: 비즈니스 로직에 따라 채팅방 생성
   POST /api/v1/rooms { participants }
        ↓
4. 모바일 앱: 실시간 room.created 이벤트 수신
   SDK.onRoomCreated → UI에 새 채팅방 표시
        ↓
5. 사용자: 메시지 전송/수신 (SDK가 처리)
        ↓
6. OiChat: 오프라인 유저 감지 → Webhook 전송
   POST your-server.com/webhook { push.notification }
        ↓
7. 테넌트 서버: 자체 푸시 발송

다음 단계