Package Information
Available Nodes
Documentation generating…
Documentation
@authyo/n8n-nodes-authyo
An n8n community node for Authyo — send and verify one-time passcodes (OTP) over SMS, WhatsApp, Voice, and Email, and manage the sessions they produce.
Installation · Credentials · Operations · Usage · Resources
Installation
n8n UI (self-hosted): Settings → Community Nodes → Install → @authyo/n8n-nodes-authyo
Manual:
cd ~/.n8n/custom
npm install @authyo/n8n-nodes-authyo
Restart n8n. "Authyo" then appears in the node panel.
Credentials
You need an Authyo application's App Key and App Secret, both found in the Authyo dashboard under your application's settings.
In n8n, create an Authyo API credential and paste them in. Press Test — this validates the pair against /auth/authentication without sending an OTP or spending credit.
Authorized Endpoint must be blank. If your Authyo application has an Authorized Endpoint (URL allow-list) configured, calls from n8n are rejected with
Invalid endpoint. n8n is a server-to-server caller and sends noOrigin/Refererheader. Either leave Authorized Endpoint blank, or add your n8n instance's egress IPs to the allow-list.
Operations
OTP
| Operation | Description |
|---|---|
| Send | Deliver a passcode to a phone number or email. Returns a mask ID identifying the OTP session. |
| Verify | Check the code the user entered against its mask ID. Returns a session JWT on success. |
Send supports an explicit channel (SMS, WhatsApp, Voice, Email) or Application Default, which uses the channel priority and fallback configured on the application. Optional: OTP length, expiry in seconds (60 second minimum), or supplying your own code.
Session
| Operation | Description |
|---|---|
| Verify Token | Check whether a session JWT is still valid. |
| Revoke | Invalidate a session token, logging the user out. |
Application
| Operation | Description |
|---|---|
| Get Configuration | Fetch the customization settings for the connected application. |
Usage
Where the mask ID lives
Send OTP returns the mask ID nested inside an array:
{{ $json.data.results[0].maskId }}
Verify OTP needs that exact value. The session JWT that Verify returns is at data.token, not at the top level.
The maskId problem — and the clean way to solve it
Send and Verify are two separate events, separated by a human typing a code. The mask ID has to survive that gap.
The cleanest solution keeps everything inside one workflow execution, so the mask ID never has to be stored anywhere:
Form Trigger (page 1 — collect email or phone)
└─ Send OTP (Authyo node, renamed "Send OTP")
└─ Form (page 2 — asks the user for the code)
└─ Verify OTP (Authyo node — maskId still in scope)
└─ IF {{ $json.success }}
├─ true → Form Ending / your action
└─ false → back to the Form page to re-prompt
The Form node continues a multi-page form on the same execution, so a previous node's output stays reachable:
{{ $('Send OTP').first().json.data.results[0].maskId }}
The node name in that expression must match the node's actual name on the canvas. The diagram above assumes the first Authyo node was renamed to
Send OTP. If you leave the default names, they will beAuthyoandAuthyo1, and the expression becomes$('Authyo').first().... Use.first()rather than.item— after a pause,.itemhas no paired input item to resolve against.
Alternative — the Wait node. If your workflow does not begin with a Form Trigger, use a Wait node with resume mode On Form Submitted instead of the Form node. It also pauses the same execution, so the same expression works. Two things to set:
- Enable Limit Wait Time and set it slightly beyond your OTP expiry, so an abandoned run does not sit forever.
- The Wait node has a single output — there is no separate timeout branch. Put an IF after it if you need to distinguish "resumed" from "timed out".
While building either version, use the Test URL and click Execute workflow. The Production URL only serves an active workflow and gives you no execution log to debug against.
Across separate workflows. If you send from one workflow and verify from another (e.g. a webhook your own front-end calls), no execution is shared, so you must persist the mask ID yourself — keyed by phone or email in Postgres/Redis — or return it to the front-end and have it send the value back on verify.
Error handling
Authyo answers HTTP 200 even on failure, signalling the real outcome via success: false in the body. n8n keys off the status code, so this node inspects the body and raises a proper n8n error — invalid credentials, a blocked account, or a rate limit will fail the node rather than silently continuing down the success branch.
One case is deliberately not an error: a wrong or expired code. That is the expected outcome of Verify, not a broken workflow, so it is passed through as normal data with success: false. Branch on it with an IF node:
{{ $json.success }} → true: verified false: wrong or expired code
{{ $json.message }} → e.g. "Invalid OTP", "OTP has expired"
This means you do not need Continue-On-Fail to handle a mistyped code — and you should not enable it, because it would also swallow genuine credential and rate-limit errors, which are the ones you want to hear about.
Response fields worth knowing
| What | Where |
|---|---|
| Mask ID | data.results[0].maskId |
| Session JWT | data.token — not at the top level |
| Channel actually used | data.results[0].authType |
| Cost of the send | data.results[0].charge / .currency |
| Throttle backoff | retryAfterSeconds, when present |