Skip to main content
RapidDev - Software Development Agency
lovable-integrationsEdge Function Integration

How to Integrate Lovable with Signal

Signal has no official API. To send Signal messages from Lovable, self-host the signal-cli-rest-api Docker container on a VPS or cloud VM, register a phone number with Signal on that server, then create a Supabase Edge Function that proxies message requests to your self-hosted relay. Store the relay URL and credentials in Cloud → Secrets. This pattern is the standard approach for privacy-first Signal automation — all messages remain end-to-end encrypted.

What you'll learn

  • Why Signal has no official API and how the signal-cli-rest-api community project enables integrations
  • How to set up a self-hosted signal-cli-rest-api instance on a cloud VM or VPS
  • How to register a dedicated phone number with Signal on your relay server
  • How to write a Deno Edge Function that proxies message requests to the self-hosted relay
  • Important privacy and security considerations for self-hosted Signal integrations
Book a free consultation
4.9Clutch rating
600+Happy partners
17+Countries served
190+Team members
Intermediate13 min read60 minutesCommunicationMarch 2026RapidDev Engineering Team
TL;DR

Signal has no official API. To send Signal messages from Lovable, self-host the signal-cli-rest-api Docker container on a VPS or cloud VM, register a phone number with Signal on that server, then create a Supabase Edge Function that proxies message requests to your self-hosted relay. Store the relay URL and credentials in Cloud → Secrets. This pattern is the standard approach for privacy-first Signal automation — all messages remain end-to-end encrypted.

Privacy-first Signal messaging in Lovable via self-hosted relay

Signal's commitment to privacy means it intentionally does not offer a public API that would allow third-party servers to send messages on behalf of users. Unlike WhatsApp Business API or Telegram's Bot API, there is no official Signal Business API or bot framework. This is a deliberate design decision: a centralized API would create a single point that could be used to track communication metadata, undermining Signal's privacy model.

The community solution is signal-cli-rest-api, an open-source Docker container that wraps the signal-cli library — a Java command-line interface for Signal that operates using Signal's own protocol libraries (libsignal). When you self-host this container and register a phone number with it, that phone number becomes a Signal account on your server. The container exposes a local REST API on port 8080 that accepts messages and forwards them through Signal's encrypted infrastructure. Your server never decrypts the messages — it just acts as a Signal client.

The architecture for Lovable integration is: your Lovable frontend → Supabase Edge Function → your self-hosted signal-cli-rest-api server → Signal's infrastructure → recipient's Signal app. The Edge Function acts as a secure proxy, keeping your relay server URL and credentials out of the browser. The relay server must be publicly accessible (or accessible from Supabase's Edge Function infrastructure) for this pattern to work.

Integration method

Edge Function Integration

Signal has no official REST API for third-party developers. The community-built signal-cli-rest-api Docker container wraps the signal-cli library (based on Signal's libsignal) to provide a local REST API for sending and receiving Signal messages. Your Lovable app calls a Supabase Edge Function, which proxies requests to your self-hosted relay server. The Edge Function handles authentication to the relay and never exposes your relay endpoint to the frontend. All messages remain end-to-end encrypted.

Prerequisites

  • A Lovable project with Lovable Cloud enabled (Cloud tab visible in the editor)
  • A cloud VPS or VM (DigitalOcean Droplet, Hetzner CX11, Linode Nanode, etc.) with Docker installed
  • A dedicated phone number to register with Signal (a SIM card, Google Voice, or VoIP number that can receive SMS verification)
  • Basic familiarity with Docker and running commands on a Linux server (required for relay setup)
  • The public IP or domain name of your VPS for configuring the relay URL secret

Step-by-step guide

1

Set up signal-cli-rest-api on a self-hosted server

Signal integration requires self-hosting the signal-cli-rest-api Docker container on a server you control. This is not a browser-only workflow — you need a Linux VPS or VM with Docker installed. The server must have a publicly accessible IP address or domain name so your Supabase Edge Function can reach it. On your server, create a directory for Signal data and start the container. The official signal-cli-rest-api image is available at Docker Hub as bbernhard/signal-cli-rest-api. A basic Docker Compose configuration starts the container on port 8080 and mounts a persistent volume for Signal account data. Set the MODE environment variable to 'json-rpc' for the REST API mode. After the container starts, register your dedicated phone number with Signal. Send a POST request to http://localhost:8080/v1/register/{phone_number} where phone_number is in E.164 format (e.g., +12025551234). This triggers a verification SMS or voice call to the number. Complete verification by POST to /v1/register/{phone_number}/verify/{verification_code} with the received code. For production use, secure the relay with basic authentication or an API key by setting environment variables in the Docker Compose file. Never expose the relay directly to the public internet without authentication — anyone who can reach port 8080 can send Signal messages as your registered number. Use a firewall to restrict access to port 8080 to only Supabase's IP ranges or use authentication middleware.

docker-compose.yml
1# docker-compose.yml for signal-cli-rest-api
2version: '3'
3services:
4 signal-cli-rest-api:
5 image: bbernhard/signal-cli-rest-api:latest
6 environment:
7 - MODE=json-rpc
8 - PORT=8080
9 ports:
10 - "127.0.0.1:8080:8080" # bind to localhost only, use nginx reverse proxy for HTTPS
11 volumes:
12 - ./signal-data:/home/.local/share/signal-cli
13 restart: unless-stopped

Pro tip: Bind the container to 127.0.0.1:8080 (not 0.0.0.0:8080) and use an Nginx reverse proxy with HTTPS and HTTP Basic Auth to expose it securely. Your Supabase Edge Function needs an HTTPS URL with authentication to reach the relay.

Expected result: A running signal-cli-rest-api container on your VPS with a registered Signal phone number, accessible via an authenticated HTTPS URL.

2

Configure Nginx with HTTPS and store relay credentials in Cloud → Secrets

Your Supabase Edge Function needs to reach the relay over HTTPS with authentication. Set up an Nginx reverse proxy in front of the signal-cli-rest-api container. Install Certbot for a free Let's Encrypt SSL certificate on your domain. Configure Nginx to require HTTP Basic Auth (create credentials with htpasswd) and proxy requests to the local Docker container on port 8080. Once the relay is secured with HTTPS and Basic Auth, you have three pieces of information for Lovable Secrets: the relay base URL (https://signal.yourdomain.com), the Basic Auth username, and the Basic Auth password. In Lovable's Cloud tab → Secrets, add: SIGNAL_RELAY_URL with your full relay URL (e.g., https://signal.yourdomain.com), SIGNAL_RELAY_USERNAME with the Basic Auth username, SIGNAL_RELAY_PASSWORD with the Basic Auth password, and SIGNAL_PHONE_NUMBER with the E.164 format number registered with Signal on the relay. Lovable's infrastructure encrypts all four secrets at rest. The relay credentials grant full access to send Signal messages from your registered number — treat them with maximum care. Never paste them in Lovable chat prompts. The platform's SOC 2 Type II certification and active credential-blocking security (approximately 1,200 blocked per day) ensure credentials stay safe when stored in the Secrets panel.

/etc/nginx/sites-available/signal-relay
1# /etc/nginx/sites-available/signal-relay
2server {
3 listen 443 ssl;
4 server_name signal.yourdomain.com;
5
6 ssl_certificate /etc/letsencrypt/live/signal.yourdomain.com/fullchain.pem;
7 ssl_certificate_key /etc/letsencrypt/live/signal.yourdomain.com/privkey.pem;
8
9 location / {
10 auth_basic "Signal Relay";
11 auth_basic_user_file /etc/nginx/.htpasswd;
12
13 proxy_pass http://127.0.0.1:8080;
14 proxy_set_header Host $host;
15 proxy_set_header X-Real-IP $remote_addr;
16 }
17}

Pro tip: Use certbot certonly --nginx -d signal.yourdomain.com to obtain a Let's Encrypt certificate and configure Nginx automatically. Add certbot renew to a cron job for automatic renewal.

Expected result: Signal relay accessible at an HTTPS URL with Basic Auth, and four secrets stored in Cloud → Secrets.

3

Build the Signal message proxy Edge Function

Create an Edge Function called signal-message that proxies message requests to your self-hosted relay. The function reads the relay URL and credentials from Secrets, constructs the Basic Auth header, and POSTs to the signal-cli-rest-api send endpoint. The signal-cli-rest-api v2 send endpoint is POST /v2/send with a JSON body containing message (string), number (the sender — your registered number), and recipients (array of E.164 numbers or group IDs). For individual messages, recipients is a single-element array. The function should validate that the 'to' number is in E.164 format before sending. Add basic sanitization to prevent misuse — consider implementing rate limiting by checking a Supabase table for send counts per user per hour. Signal does not have explicit rate limits published, but sending too many messages too quickly can trigger Signal's abuse detection and potentially result in the number being flagged. Ask Lovable to generate the Edge Function. After deployment, test it by sending a message to your own Signal number to verify the relay is reachable and credentials are correct.

Lovable Prompt

Create a Supabase Edge Function called signal-message that: 1) Reads SIGNAL_RELAY_URL, SIGNAL_RELAY_USERNAME, SIGNAL_RELAY_PASSWORD, and SIGNAL_PHONE_NUMBER from env. 2) Accepts POST body with: to (E.164 phone number) and message (string, max 1000 chars). 3) Constructs Basic Auth header from username:password. 4) POSTs to {SIGNAL_RELAY_URL}/v2/send with body {message, number: SIGNAL_PHONE_NUMBER, recipients: [to]}. 5) Returns success or error response. Include validation that 'to' starts with '+' and message is not empty. Add CORS headers.

Paste this in Lovable chat

supabase/functions/signal-message/index.ts
1// supabase/functions/signal-message/index.ts
2import { serve } from 'https://deno.land/std@0.168.0/http/server.ts';
3
4const corsHeaders = {
5 'Access-Control-Allow-Origin': '*',
6 'Access-Control-Allow-Headers': 'authorization, x-client-info, apikey, content-type',
7};
8
9serve(async (req) => {
10 if (req.method === 'OPTIONS') return new Response('ok', { headers: corsHeaders });
11
12 try {
13 const relayUrl = Deno.env.get('SIGNAL_RELAY_URL')!;
14 const username = Deno.env.get('SIGNAL_RELAY_USERNAME')!;
15 const password = Deno.env.get('SIGNAL_RELAY_PASSWORD')!;
16 const fromNumber = Deno.env.get('SIGNAL_PHONE_NUMBER')!;
17
18 const { to, message } = await req.json();
19
20 if (!to || !to.startsWith('+')) {
21 throw new Error('Recipient must be in E.164 format (e.g., +12025551234)');
22 }
23 if (!message || message.trim().length === 0) {
24 throw new Error('Message cannot be empty');
25 }
26
27 const credentials = btoa(`${username}:${password}`);
28
29 const res = await fetch(`${relayUrl}/v2/send`, {
30 method: 'POST',
31 headers: {
32 'Authorization': `Basic ${credentials}`,
33 'Content-Type': 'application/json',
34 },
35 body: JSON.stringify({
36 message: message.substring(0, 1000), // Signal message length limit
37 number: fromNumber,
38 recipients: [to],
39 }),
40 });
41
42 const responseText = await res.text();
43 if (!res.ok) throw new Error(`Relay error ${res.status}: ${responseText}`);
44
45 return new Response(
46 JSON.stringify({ success: true, status: 'sent' }),
47 { headers: { ...corsHeaders, 'Content-Type': 'application/json' } }
48 );
49 } catch (error) {
50 return new Response(
51 JSON.stringify({ error: error.message }),
52 { status: 500, headers: { ...corsHeaders, 'Content-Type': 'application/json' } }
53 );
54 }
55});

Pro tip: Test the Edge Function by calling it with your own Signal number as the recipient. Check Cloud → Logs if the function fails — common issues are relay unreachable (firewall rules) or wrong Basic Auth credentials.

Expected result: A deployed signal-message Edge Function that successfully sends Signal messages via the self-hosted relay.

4

Handle inbound Signal messages with receive callbacks

signal-cli-rest-api supports receiving inbound messages by either polling the /v1/receive/{number} endpoint or configuring a webhook callback URL. The webhook approach is more suitable for Lovable — configure the relay to POST incoming messages to your Edge Function URL. In the relay configuration (via environment variables or the signal-cli-rest-api config file), set the RECEIVE_MODE to 'on-start' and configure a webhook URL pointing to your deployed Edge Function. Alternatively, create a scheduled Edge Function that polls /v1/receive/{number} every minute and stores new messages in Supabase. The polling approach is simpler but introduces up to a 1-minute delay for inbound messages. The webhook approach requires your relay to be able to make outbound HTTP requests to Supabase's Edge Function URLs. For real-time two-way messaging, the webhook pattern is recommended. Create a second Edge Function called signal-inbound that receives POST callbacks from the relay, parses the message data (sender number, message text, timestamp), and inserts rows into a signal_messages table in Supabase. Use Supabase Realtime on this table to push inbound messages to your Lovable frontend in real time. For complex two-way messaging with conversation threading and agent management, RapidDev's team can help design the full inbox architecture.

Lovable Prompt

Create a Supabase Edge Function called signal-inbound that: 1) Receives POST requests from the signal-cli-rest-api webhook with inbound message data. 2) Parses the envelope containing sender number, message text, and timestamp. 3) Inserts into a signal_messages table with: direction='inbound', from_number, to_number, message_text, received_at. 4) Returns HTTP 200. Also build a simple chat UI that subscribes to the signal_messages table via Supabase Realtime and shows incoming messages in real-time.

Paste this in Lovable chat

Pro tip: Signal message envelopes from signal-cli can contain various types (data messages, typing indicators, read receipts). Filter for dataMessage type and non-null body to process only actual text messages.

Expected result: Inbound Signal messages stored in Supabase in real-time, visible in a Lovable chat interface.

Common use cases

Encrypted alert notifications for security-sensitive operations

Send Signal notifications for high-security events where end-to-end encryption is required — authentication alerts, sensitive document access notifications, or compliance-related messages. The Edge Function triggers a message to a designated Signal contact when a defined security event occurs in your Lovable app.

Lovable Prompt

Add Signal notifications to my admin dashboard. When a user fails login 5 times in a row, call my signal-message Edge Function to send an alert to our security team's Signal number with the user's email, IP address, and timestamp. Log the alert in a security_events table in Supabase.

Copy this prompt to try it in Lovable

Private team notifications without cloud messaging intermediaries

Replace Slack or email notifications with Signal for teams that require communication to stay off third-party cloud messaging platforms. When important events occur in your Lovable app, the Edge Function sends an encrypted Signal message to a Signal group or individual contact.

Lovable Prompt

Create a notification system that sends Signal messages instead of emails for sensitive order approvals. When an order over $10,000 is placed, call my signal-message Edge Function to notify our finance team's Signal group. Include the order ID, amount, customer name, and a link to review it.

Copy this prompt to try it in Lovable

Two-way encrypted customer support channel

Build a support channel where customers can send Signal messages to your support number and your team responds via a Lovable dashboard. Inbound messages are received by the relay server and forwarded to your Edge Function callback, which stores them in Supabase and displays them in real-time in the support interface.

Lovable Prompt

Create a two-way Signal support inbox. Inbound Signal messages arrive via my signal-webhook Edge Function and are stored in a conversations table. Build a chat interface showing conversations grouped by sender number. When a support agent replies, call my signal-message Edge Function to send the response back through Signal.

Copy this prompt to try it in Lovable

Troubleshooting

Edge Function returns 'connection refused' or 'ECONNREFUSED' when calling the relay

Cause: The SIGNAL_RELAY_URL is unreachable from Supabase Edge Function infrastructure — either the VPS firewall blocks the port, the Nginx service is not running, or the URL is incorrect.

Solution: Verify your relay URL is correct and publicly accessible by testing it with curl or httpie from a different machine. Check the VPS firewall allows inbound HTTPS (port 443). Verify Nginx is running with systemctl status nginx. Open Cloud → Logs to see the specific connection error from the Edge Function.

Relay returns 401 Unauthorized even though credentials appear correct

Cause: The Basic Auth credentials in SIGNAL_RELAY_USERNAME and SIGNAL_RELAY_PASSWORD do not match the htpasswd file on the server, or there are extra spaces in the stored Secrets.

Solution: On the VPS, test the credentials directly: curl -u username:password https://signal.yourdomain.com/v2/health. If that fails, regenerate the htpasswd entry with htpasswd -n username and update SIGNAL_RELAY_PASSWORD in Cloud → Secrets with the exact password used.

typescript
1const credentials = btoa(`${username.trim()}:${password.trim()}`);

Messages appear to send successfully (200 returned) but recipients never receive them on Signal

Cause: The sender phone number is not fully registered with Signal on the relay, or the number was registered but the signal-cli data was lost (e.g., after a container restart without persistent volumes).

Solution: Check the relay logs with docker logs signal-cli-rest-api. Verify the registration is still active by calling GET /v1/accounts on the relay. If the account is gone, re-register the number. Ensure Docker volumes are properly configured to persist Signal account data across container restarts.

Signal registration fails — verification code never arrives

Cause: Signal may be rate-limiting registration attempts for the phone number, or the number is a VoIP number that Signal does not accept for registration.

Solution: Signal prefers mobile numbers from traditional carriers over VoIP numbers (Google Voice, Twilio numbers) for registration. Use a real SIM card number if possible. Wait 24 hours between registration attempts if rate-limited. Check signal-cli documentation for the --voice flag to request a voice call instead of SMS for verification.

Best practices

  • Use a dedicated phone number exclusively for Signal automation — do not use a personal number, as it will show as your personal identity to recipients
  • Secure the relay with HTTPS and strong Basic Auth credentials — never expose signal-cli-rest-api on an HTTP port or without authentication
  • Set up firewall rules on your VPS to restrict who can reach the relay — ideally whitelist Supabase's Edge Function IP ranges
  • Monitor your relay server's resource usage — signal-cli can be memory-intensive, especially for accounts with many contacts; use at least 512MB RAM
  • Keep signal-cli-rest-api updated to the latest version — Signal frequently updates its protocol, and outdated clients stop working
  • Add rate limiting in your Edge Function to prevent abuse — check how many Signal messages were sent per user per hour and reject requests over the limit
  • Inform recipients that messages come from an automated system — Signal's encryption protects the content, but recipients should know they are communicating with a bot

Alternatives

Frequently asked questions

Is using signal-cli-rest-api against Signal's terms of service?

Signal's terms of service do not explicitly permit or prohibit automated clients. Signal does discourage mass messaging and spam, and using Signal for unsolicited bulk messages would violate their policies. For legitimate use cases — internal team notifications, two-way support where recipients have opted in, or security alerts — the signal-cli approach is generally accepted by the Signal community. However, Signal may block numbers that exhibit bot-like behavior at scale.

Are messages sent via signal-cli-rest-api end-to-end encrypted?

Yes — signal-cli uses Signal's official libsignal library and the same Signal Protocol as the official app. Messages are end-to-end encrypted in transit and at rest until delivered to the recipient's device. Your relay server acts as a Signal client and does not have access to the message content after it is encrypted on the sending device.

Can I send messages to Signal groups, not just individual contacts?

Yes — signal-cli-rest-api supports group messaging. After joining or creating a group, you get a group ID that can be used in the recipients field of the send endpoint. Group ID handling changed between signal-cli versions — check the signal-cli-rest-api documentation for your version's group ID format (base64-encoded internal ID vs. group link).

What happens if my VPS goes down — are messages lost?

If your relay server goes offline, outbound messages from your Lovable app will fail with a connection error. Inbound messages from Signal contacts will be queued by Signal's servers for up to a few weeks and delivered when your relay reconnects. To avoid outbound message loss, implement retry logic in your Edge Function that retries failed sends and alerts you when the relay is unreachable.

RapidDev

Talk to an Expert

Our team has built 600+ apps. Get personalized help with your project.

Book a free consultation

Need help with your project?

Our experts have built 600+ apps and can accelerate your development. Book a free consultation — no strings attached.

Book a free consultation

We put the rapid in RapidDev

Need a dedicated strategic tech and growth partner? Discover what RapidDev can do for your business! Book a call with our team to schedule a free, no-obligation consultation. We'll discuss your project and provide a custom quote at no cost.