Documentation

n8n-nodes-befroosh

Custom n8n nodes for Befroosh. This package is the home for any n8n node we build.
It ships the Befroosh SMS node, plus the analytics-webhook pair — Befroosh Analytics
Trigger
and Befroosh Replay — driven by a shared Befroosh Analytics API credential.

Layout

apps/n8n-nodes/
├── credentials/
│   ├── NotrikaSmsApi.credentials.ts          # one per SMS provider
│   ├── BatabjamSmsApi.credentials.ts
│   ├── MeliPayamakSmsApi.credentials.ts
│   ├── BatabjamAboardSmsApi.credentials.ts
│   ├── BefrooshAnalyticsApi.credentials.ts   # baseUrl + signingSecret + apiKey
│   └── BefrooshEmailApi.credentials.ts       # SMTP host/port/user/pass/fromAddress (mirrors core)
├── lib/
│   └── signature.ts                          # local HMAC verify (self-contained) + tests
├── nodes/
│   ├── BefrooshSms/
│   ├── BefrooshAnalyticsTrigger/             # webhook trigger (verifies + emits events)
│   ├── BefrooshReplay/                        # schedule/cancel/status replay calls
│   ├── BefrooshAssignAction/                  # create + assign an admin task
│   ├── BefrooshWait/                          # pause on an offset schedule
│   ├── BefrooshStatus/                        # route by user status (10 outputs)
│   └── BefEmailOrSms/                         # subscription-alert via email or SMS
├── scripts/copy-icons.mjs  # copies .svg/.png into dist after tsc
├── tsconfig.json
└── package.json            # the "n8n" field registers nodes + credentials

Self-contained (CLAUDE.md §10): this package is published, so it imports no
@befroosh/* / workspace:* code. lib/signature.ts re-implements the webhook
signature scheme locally (mirrors apps/core/.../webhook-signer.ts); tests run with
Node's built-in runner: node --test lib/signature.test.ts.

Develop

pnpm --filter n8n-nodes-befroosh build        # tsc → dist + icons
pnpm --filter n8n-nodes-befroosh check-types   # type-check only
pnpm --filter n8n-nodes-befroosh dev           # tsc --watch

Add another node

  1. Create nodes/<MyNode>/<MyNode>.node.ts exporting a class that implements INodeType
    (and an icon next to it).
  2. If it needs secrets, add credentials/<MyApi>.credentials.ts implementing ICredentialType.
  3. Register both in the "n8n" field of package.json (point at the compiled dist/...js).
  4. pnpm --filter n8n-nodes-befroosh build.

Befroosh SMS node

One node, Provider dropdown selects which backend SMS service to use. Each provider has
its own credential (shown only when that provider is selected). Request shapes mirror
apps/core/src/providers/sms/services/*:

Provider Endpoint Auth Operations
Notrika POST api.notrikaco.ir/api/v1/messages/send/template X-Api-Key Send Message, Send OTP
Batabjam POST smsapi.pishgamrayan.com/Messages/Send Authorization token Send Message
MeliPayamak GET sms.toolzi.ir/post/Send.asmx/... username/password in query Send Message, Send OTP
Batabjam Aboard login /api/login.php → POST /api/send-bulk-sms.php bearer (from login) Send Message, Send OTP

Fields: To (Batabjam/Aboard accept comma-separated numbers), Message (Send Message)
or OTP Code (Send OTP). Set the node to Continue On Fail if a send must never break a
workflow.

Befroosh Analytics API credential

One credential (befrooshAnalyticsApi) shared by the trigger and replay nodes — it
replaces the old BEFROOSH_* env vars:

Field Used by Purpose
baseUrl Replay Core API base incl. version prefix, e.g. https://core/v1
signingSecret Trigger HMAC secret to verify X-Befroosh-Signature
apiKey Replay Per-endpoint Bearer key (awh_live_…) for the Replay API

Befroosh Analytics Trigger node

A webhook trigger that receives Befroosh analytics events and verifies them. It computes
HMAC_SHA256(signingSecret, "<t>.<rawBody>") over the raw request bytes and compares it
to the t=…,v1=… X-Befroosh-Signature header, with a Tolerance (Seconds) anti-replay
window (default 300). Invalid/stale → HTTP 401, no item emitted; a missing signing secret
throws a node error (misconfiguration, not a 401). Valid → acks 200 (responseMode: onReceived)
and emits one flattened item: eventId, type, parent, occurredAt, actorUserId, workspaceId, data, context, delivery {id, attempt, origin, replayCount, replayScheduleId}, event. Route
downstream with a Switch on type.

Test the trigger without the backend

scripts/send-test-event.mjs builds a mock analytics event, signs it with the same
HMAC_SHA256(signingSecret, "<t>.<rawBody>") scheme the core dispatcher uses, and POSTs it
straight to an n8n webhook URL — so you can exercise a workflow without a registered
endpoint or the backend fan-out. The signing secret is used locally to compute the HMAC and is
never sent over the wire; supply the same secret the credential holds.

# from apps/n8n-nodes
node scripts/send-test-event.mjs \
  --url https://dl4.mahsolist.ir:8443/webhook/befroosh-analytics \
  --secret <signingSecret> \
  --email user@example.com           # and/or --mobile 09120000000
# or: npm run send-test -- --url … --secret … --email …

Options: --type (default account.created), --workspace-id, --actor-user-id, --stale
(sign an out-of-tolerance timestamp to confirm the node returns 401), --help. The
production URL is required and the workflow must be Active (the test URL only listens
while the editor is open). Self-signed host? Set NODE_EXTRA_CA_CERTS rather than disabling TLS.

Testing the Replay node (--db)

Replay re-delivers a stored event by id, so a mock that was never persisted will 404
(ANALYTICS_WEBHOOK_EVENT_NOTFOUND). Add --db <conninfo> to also INSERT the event into the
backend's analytics_event table before posting, so the workflow's Replay node can find it:

PGPASSWORD=… node scripts/send-test-event.mjs \
  --url https://dl4.mahsolist.ir:8443/webhook/befroosh-analytics \
  --secret <signingSecret> --email user@example.com \
  --db postgresql://postgres@HOST:5440/xx_mvp

The INSERT is a local raw SQL statement run via the external psql binary (no backend import —
stays self-contained per CLAUDE.md §10); its column list mirrors migration 1781000000000 and
payload = the event's data (which is what replay rebuilds the delivered envelope from). The
endpoint must still be subscribed to the event type and the row stays within the 90-day
retention window. Note: the replayed delivery re-POSTs to the same endpoint, so design the
workflow to not replay an already-replayed event (check delivery.replayCount).

The script is plain ESM in scripts/ — tsc does not compile it and files: ["dist"] never
publishes it. npm test round-trips its output through the node's real verifySignature to
guarantee the bytes it signs are accepted.

Befroosh Replay node

An action node that calls the Replay API at {baseUrl}/analytics/replays with
Authorization: Bearer {apiKey}:

Operation Call Key params
Schedule Replay POST /analytics/replays eventId (default ={{ $json.eventId }}), schedule (default ["1d","2d","3d"])
Cancel Replay DELETE /analytics/replays/:id replayScheduleId (default ={{ $json.delivery.replayScheduleId }})
Get Status GET /analytics/replays/:id replayScheduleId

Defaults auto-wire from an upstream Befroosh Analytics Trigger item, so the common
Trigger → Switch → Replay flow needs no manual id entry. Surfaces the backend error code
and honors Continue On Fail.

Install into an n8n instance

A custom node has to be loaded by the n8n runtime (it is not importable as a workflow):

  • Self-hosted: the package is published to npm as n8n-nodes-befroosh, so the
    simplest path is Settings → Community Nodes → Install → n8n-nodes-befroosh.
    Alternatively npm i n8n-nodes-befroosh into ~/.n8n/nodes/ (or build locally and drop
    it under ~/.n8n/custom/). Restart n8n after a manual install.
  • Managed/hosted (e.g. koochaa): only works if the host allows custom/community nodes.
    If it doesn't, use the HTTP-Request / sub-workflow approach in Back/integrations/n8n/
    instead.

Discussion