better-smpp
Server

Authentication

The onBind hook — async credential checks with inbound PDUs held.

Every bind (bind_transmitter, bind_receiver, bind_transceiver) flows through the onBind hook. Its return value decides the response:

ReturnResponse
true or undefinedESME_ROK — session becomes bound
falseESME_RBINDFAIL
a CommandStatus numberthat exact status (e.g. ESME_RINVSYSID, ESME_RINVPASWD)
the promise rejectsESME_RBINDFAIL (and the session emits error)
import { CommandStatus } from "@better-smpp/core";
import { SmppServer } from "@better-smpp/server";

const server = new SmppServer({
  systemId: "MYSMSC",
  interfaceVersion: 0x50,
  onBind: async (session, pdu) => {
    const account = await accounts.find(pdu.systemId);
    if (!account) return CommandStatus.ESME_RINVSYSID;
    if (!(await account.verify(pdu.password))) return CommandStatus.ESME_RINVPASWD;
    if (!account.allows(session.remoteAddress)) return false;
    return true;
  },
});

The hook can be slow — safely

While onBind is pending, inbound PDU processing is paused: a client that optimistically pipelines submit_sm right behind its bind won't get anything processed (or lost) until authentication finishes. The original node-smpp made you orchestrate this manually with pause()/resume(); here it's built in.

What the hook can see

The pdu argument is the fully-typed bind request: systemId, password, systemType, interfaceVersion, addrTon, addrNpi, addressRange. The session argument gives you transport context before accepting:

session.remoteAddress; // real client IP (PROXY-protocol aware)
session.secure;        // TLS?

After a successful bind

  • The response carries the server's systemId (and scInterfaceVersion when interfaceVersion is configured).
  • session.systemId and session.mode ("transmitter" | "receiver" | "transceiver") are set, and session.sessionState reflects the direction — note it's the server's perspective: a client bound as transmitter puts the server session in BOUND_RX.
  • The session emits bound with the state and the bind PDU:
server.on("session", (session) => {
  session.on("bound", (state, pdu) => {
    log.info(`${pdu.systemId} bound as ${session.mode} from ${session.remoteAddress}`);
  });
});

Re-binding an already-bound session is rejected automatically with ESME_RALYBND.

On this page