A production-ready, Vercel-deployable Node.js application that securely receives inbound SMS messages via Telnyx webhooks, verifies cryptographic signatures, extracts potential verification codes (4-6 digits commonly used in 2FA), and sends email notifications through Resend.
- Real-Time Web Dashboard: Beautiful web UI to view incoming SMS messages instantly with auto-refresh
- Secure Signature Verification: Ed25519 cryptographic signature validation to prevent webhook spoofing
- Timestamp Validation: Rejects requests older than 5 minutes to prevent replay attacks
- Verification Code Extraction: Automatically detects 4-6 digit codes commonly used in 2FA (Facebook, WhatsApp, etc.)
- One-Click Code Copy: Click verification codes to copy them instantly to clipboard
- HTML Email Notifications: Beautiful, responsive HTML emails via Resend with verification code highlighting
- Multiple Recipient Support: Handles and displays all recipient phone numbers in logs and emails
- Message Storage: Automatic JSONL file storage with 5MB rotation for message archival
- Error Notifications: Slack webhook integration for email delivery failures
- Rate Limiting: Built-in rate limiting (30 req/min per IP) to prevent webhook abuse
- Password Protection: Optional basic authentication to secure the web dashboard
- Serverless-Ready: Optimized for Vercel deployment with zero configuration
- Production Security: No hard-coded secrets, proper error handling, minimal logging of sensitive data
- Node.js 18+ installed locally
- A Telnyx account with a phone number and Messaging Profile
- A Resend account for email delivery
- GitHub account for version control
- Clone and install dependencies:
git clone <your-repository-url>
cd telnyx-webhook
npm install- Create environment file:
Create a .env file in the project root:
# Telnyx webhook signing key (base64 encoded from Telnyx portal)
TELNYX_PUBLIC_KEY=your_base64_encoded_public_key_here
# Resend API key (get from https://resend.com)
RESEND_API_KEY=re_123456789
# Email configuration
EMAIL_FROM=onboarding@resend.dev # Must be a verified sender in Resend
EMAIL_TO=your-email@example.com # Your email address for notifications-
Get your Telnyx public key:
- Log into the Telnyx Portal
- Navigate to Messaging β Numbers or Messaging Profiles
- Find your webhook signing public key (or generate a new key pair)
- Copy the public key (it will be base64 encoded)
- Add it to your
.envfile
-
Start the development server:
npm run devThe server will start on http://localhost:3000.
- Test locally with ngrok:
For local testing, expose your server to the internet:
# Install ngrok if you haven't
npm install -g ngrok
# In a separate terminal, tunnel port 3000
ngrok http 3000Note the HTTPS URL (e.g., https://abc123.ngrok.io) for the Telnyx webhook configuration.
- Push your code to GitHub:
git add .
git commit -m "Initial commit: Telnyx webhook handler"
git push origin main-
Import to Vercel:
- Go to Vercel Dashboard
- Click "Add New..." β "Project"
- Import your GitHub repository
- Vercel should auto-detect the settings (framework preset: Other)
-
Configure environment variables:
In the Vercel project settings, add these environment variables:
Variable Value Required TELNYX_PUBLIC_KEYYour base64-encoded Telnyx public key Yes RESEND_API_KEYYour Resend API key (starts with re_)Yes EMAIL_FROMVerified sender (e.g., onboarding@resend.dev)Yes EMAIL_TOYour email address for notifications Yes UI_PASSWORDStrong password for web dashboard protection No (Recommended) SLACK_WEBHOOK_URLSlack webhook URL for error notifications No MESSAGES_DIRDirectory for message storage (default: ./messages)No -
Deploy:
Click "Deploy" and wait for the build to complete.
-
Get your production URL:
Note your Vercel deployment URL (e.g.,
https://your-project.vercel.app)
# Install Vercel CLI globally
npm i -g vercel
# Login to Vercel
vercel login
# Deploy to production
vercel --prod-
Log into Telnyx Portal:
Navigate to Telnyx Portal
-
Configure your Messaging Profile:
- Go to Messaging β Messaging Profiles
- Select your Messaging Profile (or create a new one)
- Click "Edit" or navigate to the "Numbers" section
-
Assign phone numbers (if not already assigned):
- In your Messaging Profile, go to "Numbers"
- Click "Buy Number" or "Assign Number"
- Assign your desired phone number(s) to this profile
-
Set the webhook URL:
- In your Messaging Profile, find the "Inbound Settings" or "Webhooks" section
- Set "Inbound Webhook URL" to:
https://your-vercel-app.vercel.app/webhookReplace
your-vercel-appwith your actual Vercel project name. -
Save configuration:
Click "Save" or "Apply" to activate the webhook.
1. Telnyx sends POST to /webhook with SMS data
2. Server validates required headers (timestamp, signature)
3. Timestamp is checked (must be within 5 minutes)
4. Ed25519 signature is verified using TELNYX_PUBLIC_KEY
5. If valid: Parse payload, extract message details
6. Scan message body for 4-6 digit verification codes
7. Send email notification via Resend (async, non-blocking)
8. Return 200 OK immediately (Telnyx requires <5s response)
Telnyx signs webhooks using Ed25519 (a modern elliptic curve signature scheme). The signature is computed over:
{ telnyx-timestamp }|{ raw_request_body }
This creates a unique signature for each request that:
- Cannot be forged without the private key
- Cannot be replayed with an old timestamp
- Verifies the exact payload wasn't modified in transit
All phone numbers assigned to the same Messaging Profile will send their inbound webhooks to the same URL. The payload includes the destination phone number (payload.to[0].phone_number), so you can:
- Handle multiple numbers with a single webhook endpoint
- Filter or route based on the destination number
- Track which number received each message
The webhook includes a beautiful, responsive web dashboard accessible at the root URL (/) that displays:
Features:
- π± Live Message Feed: Auto-refreshes every 5 seconds to show new messages
- π Verification Code Highlighting: Codes are displayed prominently in large, highlighted boxes
- π One-Click Copy: Click any verification code to copy it to clipboard instantly
- π Statistics: Shows total messages, verification codes detected, and last update time
- β° Smart Timestamps: Displays relative time (e.g., "2m ago", "Just now")
- π± Mobile Responsive: Works perfectly on phones, tablets, and desktops
- π Password Protected: Optional basic authentication (set
UI_PASSWORDenv var)
Accessing the Dashboard:
Local development:
http://localhost:3000/
Production (Vercel):
https://your-app.vercel.app/
If UI_PASSWORD is set, your browser will prompt for credentials:
- Username: (any value, ignored)
- Password: The value from your
UI_PASSWORDenvironment variable
Perfect for:
- Setting up Facebook Business verification
- WhatsApp Business API phone verification
- Any 2FA code delivery that requires quick access
- Real-time monitoring of incoming SMS
Emails are sent with beautiful HTML formatting that includes:
- Gradient header with clear subject line
- Structured field display for From, To (with multiple recipients), and Timestamp
- Highlighted verification codes with yellow background and large text
- Responsive design that works on all email clients
- Plain text fallback for email clients that don't support HTML
Example email features:
- Verification codes are displayed in 24px bold text with visual highlighting
- Multiple recipients are shown in a bulleted list
- Timestamps are formatted in locale-specific format
- Message body preserves whitespace and line breaks
All incoming messages are automatically stored in JSONL (JSON Lines) format:
- Location:
./messages/directory (configurable viaMESSAGES_DIR) - Format: One JSON object per line for easy parsing and streaming
- File naming:
messages-YYYY-MM-DD.json(daily rotation) - Auto-rotation: Files are rotated when they exceed 5MB
- Gitignored: The messages directory is automatically excluded from git
Each stored message includes:
{
"timestamp": "2026-01-28T12:00:00.000Z",
"from": "+1234567890",
"to": "+0987654321",
"toNumbers": ["+0987654321", "+1122334455"],
"body": "Your verification code is 123456",
"code": "123456",
"messageId": "telnyx-message-id",
"direction": "inbound"
}To read stored messages:
# View all messages from today
cat messages/messages-$(date +%Y-%m-%d).json
# Parse with jq
cat messages/messages-*.json | jq -s '.'
# Count messages
wc -l messages/messages-*.jsonWhen email delivery fails, the system can automatically notify you via Slack:
-
Create a Slack Incoming Webhook:
- Go to your Slack workspace settings
- Navigate to "Apps" β "Incoming Webhooks"
- Create a new webhook and copy the URL
-
Configure the webhook URL:
SLACK_WEBHOOK_URL=https://hooks.slack.com/services/YOUR/WEBHOOK/URL
-
Automatic notifications:
- Triggered only when Resend email delivery fails
- Includes error message, timestamp, and message context
- Formatted with Slack blocks for easy reading
- Does not block webhook processing
Built-in rate limiting protects your webhook from abuse:
- Limit: 30 requests per minute per IP address
- Method: In-memory tracking (resets on server restart)
- Response: HTTP 429 (Too Many Requests) with
retryAfterheader - Auto-cleanup: Old entries are automatically removed
For serverless deployments (Vercel), consider using Redis via Upstash for persistent rate limiting across function invocations:
// Example: Upgrade to Redis-based rate limiting
// npm install @upstash/redis
const { Redis } = require('@upstash/redis');
const redis = new Redis({
url: process.env.UPSTASH_REDIS_URL,
token: process.env.UPSTASH_REDIS_TOKEN
});The webhook handler now properly supports messages sent to multiple phone numbers:
- Logs: Show all recipients with count (e.g., "2 recipients (+1111111111, +2222222222)")
- Emails: Display all recipients in a bulleted list with "(Multiple Recipients)" label
- Storage: Stores both primary recipient (
to) and full array (toNumbers)
- Start your server:
npm run dev- Test with curl:
curl -X POST http://localhost:3000/webhook \
-H "Content-Type: application/json" \
-H "telnyx-timestamp: $(date +%s)" \
-H "telnyx-signature-ed25519: invalid_signature" \
-d '{"data": {"event_type": "message.received", "payload": {"from": {"phone_number": "+1234567890"}, "to": [{"phone_number": "+0987654321"}], "text": "Test message"}}}'- Check logs for incoming message and verification code detection.
-
Send an actual SMS to your Telnyx phone number from a personal phone.
-
Monitor Vercel logs:
- Go to your Vercel project dashboard
- Click "Deployments" β select the latest deployment
- Click "Logs" to view real-time logs
- Look for
[SMS]log entries showing incoming messages
-
Check your email for the notification from Resend.
Send a message containing a 4-6 digit code to test the extraction:
Your verification code is 123456Facebook: 582941WhatsApp code: 42- Note: 2-digit codes won't match (regex requires 4-6 digits)
Expected log output:
[SMS] Inbound from +1234567890 to +0987654321 at 2026-01-28T19:30:00Z: Your verification code is 123456
[SMS] Verification code detected: 123456
| Variable | Required | Description |
|---|---|---|
TELNYX_PUBLIC_KEY |
Yes | Base64-encoded Ed25519 public key from Telnyx portal |
RESEND_API_KEY |
Yes | Resend API key (get from resend.com API keys section) |
EMAIL_FROM |
Yes | Verified sender email address (e.g., onboarding@resend.dev) |
EMAIL_TO |
Yes | Recipient email address for notifications |
UI_PASSWORD |
No (Recommended) | Password to protect the web dashboard via basic auth |
SLACK_WEBHOOK_URL |
No | Slack incoming webhook URL for error notifications (optional) |
MESSAGES_DIR |
No | Directory for message storage (default: ./messages) |
PORT |
No | Port for local development (default: 3000) |
- Log into Telnyx Portal
- Navigate to Messaging β Messaging Profiles
- Select your profile or create one
- Look for "Webhook Signing Key" or "Public Key" in the settings
- Copy the key (it should be a base64-encoded string)
- If no key exists, you may need to generate one via the API or contact Telnyx support
The code is designed for easy extension. Here are some ideas:
Replace the JSONL file storage with a database:
// In processIncomingMessage():
// Use Upstash Redis, Supabase, or MongoDB
// await redis.set(`message:${messageId}`, JSON.stringify(messageData));// After processing message:
// Create GitHub issue with verification code
// await createGitHubIssue(code, { from, body });// In processIncomingMessage():
// Send all messages (not just errors) to Slack
// await sendSlackMessage({ from, body, code });// Wrap Resend call with retry logic
// await retry(() => resend.emails.send(...), { retries: 3 });// In extractVerificationCode():
// Support different code formats
const patterns = [
/\b\d{4,6}\b/, // 4-6 digits
/\b[A-Z]{2}\d{4}\b/, // 2 letters + 4 digits (e.g., AB1234)
/\b\d{3}-\d{3}\b/ // 3-3 format (e.g., 123-456)
];- Never commit secrets: Always use environment variables
- Validate timestamps: Prevents replay attacks
- Verify signatures: Ensures webhook authenticity
- Minimal payload logging: Only logs summaries, never full payloads in production
- Async notifications: Email failures don't affect webhook response
- Proper error handling: No stack traces leaked to callers
- Check environment variables: Ensure
TELNYX_PUBLIC_KEYis set correctly - Verify key format: The key must be base64-encoded (copy exactly as shown in Telnyx portal)
- Check timestamp: Ensure your server clock is accurate (NTP synchronized)
- Verify Resend API key: Check for typos in
RESEND_API_KEY - Check sender verification:
EMAIL_FROMmust be verified in Resend - Check spam folder: Email might be in spam/junk
- Review logs: Look for
[EMAIL]log entries
- Check Telnyx configuration: Verify webhook URL is correct in Messaging Profile
- Test health endpoint:
GET https://your-app.vercel.app/health - Check Vercel logs: Look for incoming requests in deployment logs
- Verify phone number assignment: Ensure number is assigned to the Messaging Profile
- Use ngrok: Telnyx cannot reach localhost directly
- Check ngrok URL: Ensure you're using the HTTPS URL
- Update Telnyx: Temporarily update webhook URL in Telnyx portal to ngrok URL for testing
MIT License - feel free to use in your projects.
For issues with:
- Telnyx webhooks: Contact Telnyx support or check their documentation
- Resend emails: Check Resend documentation or support
- This code: Review logs and ensure all environment variables are configured correctly