Panduan
Membuat aplikasi (OAuth 2.0)
Aplikasi pihak ketiga bertindak atas nama merchant TheComNet lain. Merchant menyetujui aplikasi Anda sekali; Anda mendapat token yang dibatasi pada tenant mereka melalui OAuth 2.0 authorization code flow dengan PKCE.
Mendaftarkan aplikasi
Owner atau admin tenant mana pun bisa mendaftarkan aplikasi di Developer Console → Aplikasi. Sebuah aplikasi memiliki:
- Client id (
tcn_app_…) dan client secret (tcn_cs_…). Secret hanya ditampilkan satu kali; rotasi untuk mendapat secret baru, setelah itu secret lama berhenti berfungsi. - Redirect URI, maksimal 10, dicocokkan persis (tanpa pencocokan prefix atau wildcard). Harus
https;httphanya diizinkan untuk host loopbacklocalhostdan127.0.0.1. Fragment (#…) tidak diizinkan. - Scope maksimum yang boleh diminta aplikasi. Scope-nya sama dengan API key; lihat tabel scope.
- Opsional, URL webhook dan event-nya, dikirim untuk setiap instalasi (lihat Webhook aplikasi).
Siklus hidup aplikasi
| Status | Siapa yang bisa mengotorisasi | Catatan |
|---|---|---|
development | Hanya tenant milik developer | Untuk pengujian. Aplikasi baru dimulai di sini. |
in_review | Hanya tenant milik developer | Diatur oleh Ajukan review, yang mewajibkan deskripsi dan website. |
published | Tenant mana pun | TheComNet menyetujui aplikasi. |
rejected | Hanya tenant milik developer | Catatan review menjelaskan alasannya. Perbaiki aplikasi lalu ajukan lagi. |
suspended | Tidak ada | Semua token berhenti berfungsi dan endpoint token menjawab unauthorized_client. |
Tenant lain yang membuka authorization URL aplikasi yang belum dipublikasikan akan melihat error di TheComNet (OAUTH_APP_NOT_AVAILABLE).
Alur
- 1Aplikasi AndaMembuat state dan PKCE code_verifier, menyimpan keduanya di sesi pengguna, lalu me-redirect browser ke /oauth/authorize.
- 2TheComNetMeminta pengguna masuk, memilih akun toko (tenant) tempat mereka menjadi owner/admin, lalu menampilkan layar persetujuan.
- 3MerchantMenyetujui (atau menolak) scope yang diminta.
- 4TheComNetMe-redirect browser ke <redirect_uri>?state=…&code=… (atau error=access_denied).
- 5Aplikasi AndaMemeriksa state, lalu mengirim POST code + code_verifier ke /v1/oauth/token dengan client credential-nya (di sisi server).
- 6TheComNetMengembalikan access token (1 jam), refresh token (90 hari), scope yang diberikan, tenant_id, dan installation_id.
- 7Aplikasi AndaMemanggil API dengan Authorization: Bearer tcn_at_…, dan melakukan refresh sebelum access token kedaluwarsa.
1. Authorization request
Arahkan browser merchant ke https://app.comnethub.app/oauth/authorize:
https://app.comnethub.app/oauth/authorize
?client_id=tcn_app_…
&redirect_uri=https%3A%2F%2Fexample.com%2Foauth%2Fcallback
&scope=orders%3Aread%20products%3Aread
&state=Zk3q…
&code_challenge=E9Melhoa2OwvFrEMTJguCHaoeK1t8URWbuGJSstw-cM
&code_challenge_method=S256| Parameter | Wajib | Arti |
|---|---|---|
client_id | ya | Client id aplikasi Anda. |
redirect_uri | ya | Salah satu redirect URI terdaftar, persis. |
scope | tidak | Scope dipisah spasi, subset dari scope aplikasi. Kosong atau tidak ada berarti semua scope aplikasi. |
state | disarankan | Nilai opaque yang dikembalikan saat redirect. Ikat ke sesi pengguna dan periksa (CSRF). |
code_challenge | disarankan | SHA-256 base64url dari code_verifier, tanpa padding (43 karakter). |
code_challenge_method | bila ada challenge | S256 |
PKCE disarankan untuk setiap aplikasi dan praktis wajib untuk public client atau native client, yang tidak bisa menyimpan secret. Bila challenge dikirim, token request harus membawa code_verifier yang cocok.
Merchant masuk, memilih akun toko (tenant) tempat mereka menjadi owner atau admin, lalu menyetujui. Tenant yang memasang butuh paket Pro (akses API); bila tidak, layar persetujuan akan menyebutkannya. Mengotorisasi ulang untuk instalasi yang sudah ada akan mengganti scope-nya.
Redirect kembali ke aplikasi Anda
https://example.com/oauth/callback?state=Zk3q…&code=8fKq…
https://example.com/oauth/callback?state=Zk3q…&error=access_denied- Code berlaku 10 menit dan hanya bisa dipakai sekali.
client_idyang tidak dikenal atauredirect_uriyang tidak terdaftar ditampilkan sebagai error di TheComNet dan tidak pernah di-redirect, sehingga request palsu tidak bisa memantulkan pengguna ke situs sembarang.
2. Menukar code
POST https://api.comnethub.app/v1/oauth/token- Body:
application/x-www-form-urlencoded(JSON juga diterima). - Autentikasi client: HTTP Basic dengan
client_id:client_secret, atauclient_iddanclient_secretdi body. grant_type=authorization_codedengancode,redirect_uri(nilai yang sama seperti di authorization request), dancode_verifier(bila challenge dikirim).
curl -X POST "https://api.comnethub.app/v1/oauth/token" \
-u "$THECOMNET_CLIENT_ID:$THECOMNET_CLIENT_SECRET" \
-d grant_type=authorization_code \
-d code="$CODE" \
--data-urlencode redirect_uri="https://example.com/oauth/callback" \
-d code_verifier="$CODE_VERIFIER"{
"access_token": "tcn_at_…",
"token_type": "Bearer",
"expires_in": 3600,
"refresh_token": "tcn_rt_…",
"scope": "orders:read products:read",
"tenant_id": "0192f0c0-1111-7aaa-8bbb-0123456789ab",
"installation_id": "0192f0e1-2c3d-7e4f-8a5b-6c7d8e9fa0b1"
}scope adalah yang diberikan merchant. Simpan koneksi Anda dengan kunci tenant_id (atau installation_id).
3. Refresh token
Access token berlaku satu jam (expires_in: 3600). Dapatkan pasangan baru dengan grant_type=refresh_token:
curl -X POST "https://api.comnethub.app/v1/oauth/token" \
-u "$THECOMNET_CLIENT_ID:$THECOMNET_CLIENT_SECRET" \
-d grant_type=refresh_token \
-d refresh_token="$REFRESH_TOKEN"- Refresh token berlaku 90 hari dan berotasi setiap dipakai: response membawa refresh token baru dan yang lama berhenti berfungsi.
- Simpan refresh token baru secara atomik bersama access token, sebelum memakainya. Serialkan refresh per instalasi.
- Menyerahkan refresh token yang sudah dipakai, atau memutar ulang authorization code, diperlakukan sebagai pencurian: semua token instalasi dicabut dan merchant harus mengotorisasi ulang.
Error endpoint token
Endpoint token dan revoke menjawab dalam format OAuth (RFC 6749), bukan problem details:
{
"error": "invalid_grant",
"error_description": "Authorization code expired"
}| error | Status | Kapan |
|---|---|---|
invalid_request | 400 | Parameter wajib tidak ada (misalnya token pada revoke). |
invalid_client | 401 | Client id tidak dikenal atau secret salah. Dikirim dengan header www-authenticate: Basic. |
invalid_grant | 400 | Code atau refresh token tidak dikenal, kedaluwarsa, atau dipakai ulang; redirect_uri atau code_verifier tidak cocok; aplikasi sudah dicopot. |
unauthorized_client | 400 | Aplikasi sedang ditangguhkan. |
unsupported_grant_type | 400 | grant_type bukan authorization_code atau refresh_token. |
Tangani invalid_grant saat refresh dengan menandai koneksi sebagai terputus dan meminta merchant menghubungkan ulang. Endpoint token dan revoke dibatasi 120 request per menit per IP. Lihat juga Error.
Mencabut token
POST https://api.comnethub.app/v1/oauth/revoke dengan token=… (RFC 7009) dan autentikasi client yang sama. Selalu menjawab 200 untuk client yang terautentikasi, termasuk untuk token yang tidak dikenal.
curl -X POST "https://api.comnethub.app/v1/oauth/revoke" \
-u "$THECOMNET_CLIENT_ID:$THECOMNET_CLIENT_SECRET" \
-d token="$REFRESH_TOKEN"- Mencabut refresh token mengakhiri semua token instalasi (aplikasi tetap terpasang tetapi harus diotorisasi ulang).
- Mencabut access token hanya mengakhiri token itu.
4. Memanggil API
curl "https://api.comnethub.app/v1/developer/token-info" \
-H "Authorization: Bearer $ACCESS_TOKEN"- App token memakai endpoint dan scope yang sama dengan API key; lihat referensi API dan tabel scope. Endpoint yang tidak terbuka untuk API key menjawab 403
API_KEY_NOT_ALLOWED. - Rate limit: 600 request per menit per instalasi. Lihat Rate limit.
- Token bertindak sebagai anggota yang memasang aplikasi. Bila anggota itu dikeluarkan atau kehilangan owner/admin, panggilan dijawab 401 dan tenant harus mengotorisasi ulang.
{
"tenantId": "0192f0c0-1111-7aaa-8bbb-0123456789ab",
"kind": "app",
"keyId": null,
"keyName": null,
"appId": "0192f0e0-1b2c-7d3e-8f4a-5b6c7d8e9f0a",
"appName": "Acme ERP Sync",
"installationId": "0192f0e1-2c3d-7e4f-8a5b-6c7d8e9fa0b1",
"scopes": [
"orders:read",
"products:read"
],
"sandbox": false
}Contoh lengkap
Aplikasi Express: redirect dengan PKCE dan state, callback, penukaran code, refresh, dan panggilan API.
import crypto from 'node:crypto';
import express from 'express';
import session from 'express-session';
const APP = 'https://app.comnethub.app';
const API = 'https://api.comnethub.app';
const CLIENT_ID = process.env.THECOMNET_CLIENT_ID; // tcn_app_…
const CLIENT_SECRET = process.env.THECOMNET_CLIENT_SECRET; // tcn_cs_…, server-side only
const REDIRECT_URI = 'https://example.com/oauth/callback'; // registered exactly
const SCOPES = 'orders:read products:read';
const app = express();
app.use(session({ secret: process.env.SESSION_SECRET, resave: false, saveUninitialized: false }));
const b64url = (buf) => buf.toString('base64url');
// 1. Send the merchant to TheComNet with state + PKCE.
app.get('/connect', (req, res) => {
const state = b64url(crypto.randomBytes(24));
const verifier = b64url(crypto.randomBytes(32)); // 43 chars
const challenge = b64url(crypto.createHash('sha256').update(verifier).digest());
req.session.oauth = { state, verifier };
const qs = new URLSearchParams({
client_id: CLIENT_ID,
redirect_uri: REDIRECT_URI,
scope: SCOPES,
state,
code_challenge: challenge,
code_challenge_method: 'S256',
});
res.redirect(`${APP}/oauth/authorize?${qs}`);
});
// Token endpoint call with HTTP Basic client auth.
async function tokenRequest(params) {
const res = await fetch(`${API}/v1/oauth/token`, {
method: 'POST',
headers: {
Authorization: 'Basic ' + Buffer.from(`${CLIENT_ID}:${CLIENT_SECRET}`).toString('base64'),
'Content-Type': 'application/x-www-form-urlencoded',
},
body: new URLSearchParams(params),
});
const body = await res.json();
if (!res.ok) throw Object.assign(new Error(body.error_description ?? body.error), { oauth: body.error });
return body;
}
// 2. Callback: verify state, exchange the code (valid 10 min, single use).
app.get('/oauth/callback', async (req, res) => {
const saved = req.session.oauth;
delete req.session.oauth;
if (!saved || req.query.state !== saved.state) return res.status(400).send('Invalid state');
if (req.query.error) return res.status(400).send(`Not connected: ${req.query.error}`);
const t = await tokenRequest({
grant_type: 'authorization_code',
code: String(req.query.code),
redirect_uri: REDIRECT_URI,
code_verifier: saved.verifier,
});
await db.saveConnection({
tenantId: t.tenant_id,
installationId: t.installation_id,
accessToken: t.access_token,
refreshToken: t.refresh_token,
scope: t.scope,
expiresAt: Date.now() + t.expires_in * 1000,
});
res.send('Connected');
});
// 3. Refresh shortly before expiry. Refresh tokens rotate: store the new pair atomically.
async function accessTokenFor(tenantId) {
const c = await db.getConnection(tenantId);
if (c.expiresAt - Date.now() > 60_000) return c.accessToken;
try {
const t = await tokenRequest({ grant_type: 'refresh_token', refresh_token: c.refreshToken });
await db.updateTokens(tenantId, {
accessToken: t.access_token,
refreshToken: t.refresh_token,
expiresAt: Date.now() + t.expires_in * 1000,
});
return t.access_token;
} catch (e) {
// Uninstalled, expired, or replayed: ask the merchant to reconnect.
if (e.oauth === 'invalid_grant') await db.markDisconnected(tenantId);
throw e;
}
}
// 4. Call the API as the installation.
async function listOrders(tenantId) {
const res = await fetch(`${API}/v1/orders?limit=50`, {
headers: { Authorization: `Bearer ${await accessTokenFor(tenantId)}` },
});
if (res.status === 401) await db.markDisconnected(tenantId); // uninstalled or installer lost access
if (!res.ok) throw new Error(`${res.status} ${(await res.json()).code}`);
return res.json();
}
app.listen(3000);Pencopotan
Merchant bisa mencopot aplikasi Anda kapan saja di Pengaturan → Integrasi → Aplikasi terpasang. Access token-nya langsung berhenti (401) dan refresh gagal dengan invalid_grant. Tangani keduanya dengan menandai koneksi sebagai terputus dan menawarkan sambung ulang.
Webhook aplikasi
Bila aplikasi memiliki URL webhook dan event, setiap tenant yang memasang otomatis mendapat endpoint webhook yang dikelola aplikasi.
- Pengiriman ditandatangani dengan webhook secret aplikasi, memakai skema yang sama seperti Webhook. Satu secret mencakup semua instalasi.
tenantIddi envelope menunjukkan instalasi pemilik event.- Mengubah URL atau event, atau merotasi secret, berlaku untuk semua instalasi sekaligus.
- Tenant bisa melihat endpoint tersebut tetapi tidak bisa mengubah atau menghapusnya (
WEBHOOK_MANAGED_BY_APP). Mencopot aplikasi akan menghapusnya.
Praktik terbaik
- Simpan client secret di sisi server. Jangan pernah menyertakannya di kode browser atau mobile.
- Buat
statebaru per request dan verifikasi di callback. - Pakai PKCE (
S256) untuk setiap authorization request. - Refresh sesaat sebelum kedaluwarsa, satu refresh per instalasi pada satu waktu, dan simpan token hasil rotasi secara atomik.
- Saat menerima
invalid_grantatau 401, minta merchant menghubungkan ulang alih-alih mengulang request. - Minta scope sesedikit mungkin yang dibutuhkan aplikasi.
Uji dengan tenant Anda sendiri selama aplikasi berstatus development (sandbox mode), lalu tekan Ajukan review.