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/json2JWT 토큰 발급
모바일 사용자가 로그인하면, 테넌트 서버가 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
| Method | Path | 설명 |
|---|---|---|
| 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' },
});
}| Method | Path | 설명 |
|---|---|---|
| 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-signature | HMAC-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. 테넌트 서버: 자체 푸시 발송다음 단계
- →Flutter SDK 빠른 시작
- →React SDK 빠른 시작
- →라이브 데모 체험
- →가격 플랜 확인 (Server Mode는 Starter 이상)