Skip to content

Authentication and signing ​

Public API requests use AppKey / AppSecret with HMAC-SHA256 signatures. They do not depend on console login sessions or browser cookies.

Request headers ​

HeaderDescription
OC-App-KeyAppKey for the current workspace
OC-TimestampUnix timestamp in milliseconds, within 5 minutes of platform time
OC-NonceNew random value per request, 16–64 characters: letters, digits, - or _
OC-Signaturev1= followed by the lowercase hexadecimal HMAC-SHA256 result
X-Request-IDOptional troubleshooting identifier, up to 64 characters

Keep AppSecret on your server. Do not include it in webpages, mobile bundles or model context.

Signing content ​

Join these seven lines with newline characters, without a trailing newline:

text
OC-HMAC-SHA256
{UPPERCASE HTTP method}
{raw request path}
{raw query string, without the question mark}
{OC-Timestamp}
{OC-Nonce}
{SHA-256 of the request body, lowercase hex}

Keep line four empty when there is no query string. Use the path and query exactly as sent, without additional sorting or decoding. Hash an empty byte sequence when there is no body.

Authentication self-check ​

GET /api/open/v1/auth/check and POST /api/open/v1/auth/check are implemented for signature validation. The platform supplies the API domain during integration.

This Node.js example calls only the self-check endpoint; it does not submit business messages:

js
import { createHash, createHmac, randomBytes } from 'node:crypto'

const { OMNICHAT_BASE_URL, OMNICHAT_APP_KEY, OMNICHAT_APP_SECRET } = process.env
if (!OMNICHAT_BASE_URL || !OMNICHAT_APP_KEY || !OMNICHAT_APP_SECRET) {
  throw new Error('Configure the API domain, AppKey and AppSecret on your server')
}
const url = new URL('/api/open/v1/auth/check', OMNICHAT_BASE_URL)
if (url.protocol !== 'https:') throw new Error('Public APIs require HTTPS')
const body = JSON.stringify({ ping: 'hello' })
const timestamp = String(Date.now())
const nonce = randomBytes(16).toString('hex')
const digest = createHash('sha256').update(body, 'utf8').digest('hex')
const canonical = ['OC-HMAC-SHA256', 'POST', url.pathname,
  url.search.slice(1), timestamp, nonce, digest].join('\n')
const signature = createHmac('sha256', OMNICHAT_APP_SECRET)
  .update(canonical, 'utf8').digest('hex')

const response = await fetch(url, {
  method: 'POST',
  headers: {
    'Content-Type': 'application/json',
    'OC-App-Key': OMNICHAT_APP_KEY,
    'OC-Timestamp': timestamp,
    'OC-Nonce': nonce,
    'OC-Signature': `v1=${signature}`,
  },
  body,
  signal: AbortSignal.timeout(10000),
})
console.log(response.status, await response.text())

Troubleshooting ​

  • Signature mismatch: check that the sent body bytes match those used for the hash.
  • Expired request: check your server clock and millisecond timestamp.
  • Duplicate nonce: generate a fresh nonce and signature for every retry.
  • Wrong workspace: verify that the credentials belong to your target workspace. Do not switch workspaces through the request body.

Callbacks use a separate signing key. See message callbacks.

Errors and rate limits ​

Business endpoints use POST JSON; the signature-check endpoint also supports GET. Request bodies are limited to 1 MB. The default rate limit is 600 requests per AppKey per calendar minute and may vary by deployment. A success response is {"code":"OK","message":"success","data":{}}. An error example:

json
{"code":"CONFLICT","message":"Check the current resource state","data":null}
HTTPcodeHandling
400INVALID_ARGUMENTCorrect fields or configuration
401AUTH_REQUIRED / INVALID_CREDENTIALSCheck credentials and signing
401REQUEST_EXPIREDSynchronize clocks and re-sign; OC-Server-Time gives platform milliseconds
401REQUEST_REPLAYEDGenerate a fresh nonce and signature; preserve the business eventId
403CREDENTIAL_DISABLED / FORBIDDENCheck workspace and credential status and IP allowlists
404NOT_FOUNDSync the user or check character identifiers
409CONFLICT / VERSION_CONFLICTRead current state before updating or retrying
413PAYLOAD_TOO_LARGEReduce body size
429RATE_LIMITEDBack off and retry with a fresh signature
503SERVICE_UNAVAILABLETemporarily unavailable; retry later

A network timeout does not prove the operation was not executed. Preserve eventId for chat retries. Query state after character creation/publication timeouts instead of retrying blindly. X-Request-ID is for tracing, not business deduplication.

Download the Node.js request-signing and callback-verification example; usage is shown in the quick start.

OmniChat · Let conversations happen naturally