FAQ and troubleshooting
First note down the SDK version, Node.js version, code, reason, op and requestId, then compare with the cases below. Don't branch on error message text; see Error handling for an example.
Install and run
node --version
npm config get registry
npm view @deeprespond/im-server-sdk@1.0.7 version --registry=https://registry.npmjs.org/| Symptom | What to do |
|---|---|
| npm 404 or ETARGET | The package is @deeprespond/im-server-sdk, currently public at 1.0.7. A private mirror may not have synced; use the official registry install command below |
| Unsupported engine | Node.js must be at least 22.17.0; check the version in the container or process that actually runs the service |
| Cannot use import outside a module | For JavaScript, use .mjs or set type: module in package.json; for CommonJS, use require() |
| client.js from the examples isn't found | Compile the TypeScript examples with NodeNext first and run the generated JavaScript; see the examples guide |
| TypeScript can't find subpath types | Use moduleResolution: NodeNext, node16 or bundler, install @types/node@22, and don't use the old node10 resolution |
| Creating the client fails in a browser or Edge | The package uses server credentials and Node.js features; set runtime = 'nodejs' on Next.js routes, and use the client Web SDK in web pages |
npm install @deeprespond/im-server-sdk@1.0.7 --registry=https://registry.npmjs.org/This command only sets the registry for this install, without changing project or global npm config.
Credentials and identities
| Name | Where it comes from | Where it's used |
|---|---|---|
AppKey: org name # app name | App info in the console | Passed as orgName and appName |
| Client ID, Client Secret | Server credentials in the console | Creating DRClient; the SDK exchanges them for the App Token |
| App Token | Fetched, cached and refreshed by the SDK | Calling OpenAPI as the app administrator; never given to end users |
| User sign-in ticket | Your back end calls users.issueLoginTicket() for an authenticated business user | Given to that user's client SDK to sign in |
| Callback endpoint ID, signing secret | Callback settings in the console | CallbackHandler.secrets, managed separately from the Client Secret |
The server SDK has app management permissions. Your back end must check user identity and permissions itself: for example, never let an arbitrary username in a request decide who gets a ticket, and don't use admin recall or admin history queries directly as regular-user endpoints.
Authentication and network
| Symptom | Check in this order |
|---|---|
unauthenticated, or the first request fails to get a token | baseUrl points to the right environment; org name, app name and Client ID/Secret come from the same app; the credentials haven't been revoked; whether the error's op is app.token |
client_unavailable | Look at reason and details.cause; after a temporary failure it may be in a cooldown. If the app was deleted or the tenant closed, isPermanentlyUnavailable is true and retrying won't help |
permission_denied | Check the app's status and the feature or policy the API needs, and follow that REST API's details |
network_error | Check the target domain, port, container network, DNS, proxy and certificate chain; don't mistake the console web address for the API root |
| HTTPS_PROXY is set but requests go direct | The SDK doesn't read proxy environment variables; use a custom fetch with ProxyAgent. See Proxies and private certificates |
| Private CA certificate verification fails | Set NODE_EXTRA_CA_CERTS before starting the process, make sure the certificate file is readable inside the service container, and keep TLS verification on |
When updating app credentials, call im.updateCredentials(newClientId, newClientSecret), or update the config and restart the process. Never log the Secret, App Token, tickets or signed download URLs.
Timeouts, rate limits and duplicate operations
| Symptom | What it means and what to do |
|---|---|
timeout | One attempt or the whole call ran out of time, or AbortSignal.timeout() fired. Check timeoutMs and totalTimeoutMs, both in milliseconds |
canceled | The caller cancelled, or the original client is closing; end this flow as your business requires |
rate_limited | The SDK already waited as configured or reached its wait limit. error.retryAfter is in seconds and maxRetryAfterMs in milliseconds; don't mix them up |
| A write timed out and you don't know whether it succeeded | Look up the result on the server first; when retrying the same business action, reuse the message's client_msg_id or the channel's operation_id rather than generating a new one each time |
| The HTTP call succeeded but some batch items failed | Check result.ok and failed_items; throwIfFailed() throws a BatchError, and handle each failure reason item by item |
version_conflict | Read the object's current version again, then let your business decide whether to update; don't overwrite unconditionally and resubmit |
isTemporary means the error is usually recoverable, not that any write is safe to resend. For the SDK's automatic retry categories, see the method reference; your own job retries still need idempotency keys.
Callbacks
First go through "create the endpoint → start the receiver → verify the endpoint", then trigger the events. It only works if the server can reach your receiver; your own browser reaching it doesn't prove the remote server can.
| HTTP status or symptom | What to check |
|---|---|
| 401 | The endpoint ID, the secrets array, system time and the raw request body; sending the same signed request again is rejected by the replay check |
| 400 | The signature passed but the request doesn't match the protocol; look at the receiver's logs, and don't send ordinary business JSON to the callback endpoint |
| 413 | Check the reverse proxy, the framework's body parser and the SDK's maxBodyBytes together; the SDK's default limit is 2 MiB |
| 503 | Looking up secrets, Redis or your handler failed, the event is being handled by another instance, or a sync callback timed out; check the handler's logs |
| Signature verification fails in Express | Put the callback route before express.json(), or keep the raw Buffer; re-serializing a parsed object with JSON.stringify can't replace the original |
| Next.js works locally but handles events twice in production | Check whether it's deployed as several instances; use a shared Redis with the same keyPrefix everywhere instead of per-instance in-memory de-duplication |
| Events get 204 but your handler doesn't run | Check the subscribed and registered event names; unknown events and known events without a handler may be ignored. Use onAny() to observe everything |
| A sync callback returns 200 but the expected policy didn't apply | Check the SDK Verdict returned, which sync hooks are actually enabled, and the server config; sync callbacks and event subscriptions are configured separately |
Callbacks can be redelivered. If the process exits after your business write succeeds, the same event may still arrive again, so make database writes and enqueuing idempotent by event ID too. For shared storage and time limits, see Receiving callbacks.
Files and standalone channels
When uploading, in-memory sources of ordinary files need name, group files need group_id, and user avatars need owner. The SDK doesn't accept a Readable stream as a re-readable upload source; write it to disk first or use a Buffer or Blob. To download, pass the media url, and the SDK gets a temporary download address; see Uploading and downloading files.
client.channels manages standalone RTC channels; client.calls manages IM calls. After creating a channel and issuing channel tickets, users still join the media through the matching client API. Channel paging uses next_cursor, with no channels.iter(); for the version fields needed to update channels and bans, see the channel reference.
Information to send when asking for help
Give operations or support the time it happened with its timezone, the environment, the SDK and Node versions, the API name, the error code and the requestId. A minimal redacted code sample is enough, keeping the real field names and timeout settings; never send credentials or user sign-in tickets with it.
