Eclair Integration (Lightning)
Eclair is a Lightning Network implementation in Scala by ACINQ. Unlike LND with its monolithic architecture, Eclair strictly follows BOLT specifications and is known for production reliability: it powers Phoenix wallet (ACINQ mobile Lightning wallet) and several major services. If your stack is JVM-based or you're already working with ACINQ ecosystem — this is natural choice.
Running and configuration
Requirements
Eclair requires Bitcoin Core node for blockchain data access. Neutrino (simplified mode) is not supported by Eclair — needs full node.
eclair.conf (Typesafe Config format):
eclair {
chain = "mainnet"
server.port = 9735
api.enabled = true
api.port = 8080
api.password = "your-api-password"
bitcoind {
host = "localhost"
rpcport = 8332
rpcuser = "bitcoinrpc"
rpcpassword = "rpcpassword"
zmqblock = "tcp://127.0.0.1:28334"
zmqtx = "tcp://127.0.0.1:28335"
}
# Fee policy for routing
router.path-finding.default.max-fee-flat-sat = 21
router.path-finding.default.max-fee-proportional = 0.01 // 1%
# Channel limits
max-htlc-value-in-flight-msat = 100000000000 // 1 BTC in msat
}
HTTP API: main operations
Eclair provides REST API (form-encoded POST requests, not JSON body — often causes confusion):
# Node information
curl -u :your-password http://localhost:8080/getinfo
# Open channel
curl -u :your-password http://localhost:8080/open \
-d nodeId=<peer_pubkey> \
-d fundingSatoshis=1000000 \
-d pushMsat=0
# Create invoice
curl -u :your-password http://localhost:8080/createinvoice \
-d description="Payment for order 123" \
-d amountMsat=50000000 \
-d expireIn=3600
# Send payment
curl -u :your-password http://localhost:8080/payinvoice \
-d invoice=lnbc500u1p... \
-d blocking=true
TypeScript client
import axios from "axios";
import FormData from "form-data";
class EclairClient {
private readonly http = axios.create({
baseURL: `http://${this.host}:${this.port}`,
auth: { username: "", password: this.password },
});
async createInvoice(params: {
amountMsat: number;
description: string;
expireIn?: number;
}): Promise<{ serialized: string; paymentHash: string }> {
const form = new FormData();
form.append("amountMsat", params.amountMsat.toString());
form.append("description", params.description);
if (params.expireIn) form.append("expireIn", params.expireIn.toString());
const { data } = await this.http.post("/createinvoice", form, {
headers: form.getHeaders(),
});
return data;
}
async payInvoice(invoice: string, maxFeeMsat?: number): Promise<PaymentResult> {
const form = new FormData();
form.append("invoice", invoice);
form.append("blocking", "true"); // wait for result
if (maxFeeMsat) form.append("maxFeeFlatMsat", maxFeeMsat.toString());
const { data } = await this.http.post("/payinvoice", form, {
headers: form.getHeaders(),
});
return data;
}
async getPayment(paymentHash: string): Promise<PaymentStatus> {
const form = new FormData();
form.append("paymentHash", paymentHash);
const { data } = await this.http.post("/getsentinfo", form, {
headers: form.getHeaders(),
});
return data[0];
}
}
WebHooks: real-time events
Eclair supports WebHook notifications for events — main way to react to incoming payments without polling:
// eclair.conf
eclair.api.webhooks = [
{
id = "my-backend"
url = "https://your-backend.com/eclair/webhook"
secret = "webhook-secret-for-hmac"
}
]
Event types:
type EclairEvent =
| { type: "payment-received"; paymentHash: string; amount: number; timestamp: number }
| { type: "payment-sent"; paymentHash: string; amount: number; feesPaid: number }
| { type: "payment-failed"; paymentHash: string; failures: string[] }
| { type: "channel-opened"; channelId: string; remotePubkey: string; capacity: number }
| { type: "channel-closed"; channelId: string; reason: string };
Webhook handler with signature verification:
app.post("/eclair/webhook", (req, res) => {
const signature = req.headers["x-eclair-hmac"];
const expectedSig = createHmac("sha256", WEBHOOK_SECRET)
.update(JSON.stringify(req.body))
.digest("hex");
if (signature !== expectedSig) {
return res.status(401).send("Invalid signature");
}
const event: EclairEvent = req.body;
if (event.type === "payment-received") {
handleIncomingPayment(event.paymentHash, event.amount);
}
res.sendStatus(200);
});
Eclair specifics compared to LND
BOLT-12 support — Eclair implements BOLT-12 Offers earlier and more completely than LND. If you need reusable payment codes or offline payment schemes — Eclair is advantage.
Trampoline routing — mechanism for delegating pathfinding to intermediate node. Critical for mobile clients (Phoenix uses this): lightweight client doesn't want to store full network graph. Eclair is one of few implementations with production Trampoline support.
# Send via trampoline
curl -u :password http://localhost:8080/payinvoice \
-d invoice=lnbc... \
-d trampolineNodeId=<trampoline_pubkey> \
-d blocking=true
API style: form-encoded instead of JSON. This is not a bug, it's design decision. Converting JSON to form data — one extra line.
Metrologia: Eclair exports metrics via built-in Kamon instrumentation. Prometheus endpoint enabled by config:
kamon.prometheus.embedded-server.port = 9095
Typical integration scenarios
Merchant payments
- User chooses "pay with Lightning" → POST
/createinvoice→ QR code / BOLT-11 string - Webhook
payment-received→ update order status in DB - Duplicate polling via
/getreceivedinfofor reliability (in case of missed webhook)
Bulk payouts
async function bulkPayout(payments: { invoice: string; maxFeeMsat: number }[]) {
const results = await Promise.allSettled(
payments.map((p) => eclairClient.payInvoice(p.invoice, p.maxFeeMsat))
);
const failed = results
.map((r, i) => ({ result: r, payment: payments[i] }))
.filter((r) => r.result.status === "rejected");
// Log errors, retry with exponential backoff
for (const { payment, result } of failed) {
logger.error("Payment failed", { invoice: payment.invoice, reason: result });
}
}
Fee policy for routing node
Eclair allows setting individual fee policies per channel:
# Update policy for specific channel
curl -u :password http://localhost:8080/updaterelayfee \
-d channelId=<channel_id> \
-d feeBaseMsat=1000 \
-d feeProportionalMillionths=100 # 0.01%
Monitoring production node
Key metrics for Eclair:
-
channels.countby state (NORMAL, CLOSING, OFFLINE) -
payment.sent.success_rate— success rate for outgoing payments -
payment.received.count/amount— incoming flow -
router.graph.nodesandchannels— network size visible to node
Grafana dashboard with these metrics — standard operational necessity for any Lightning node with more than few channels.
Timeline for Eclair integration into existing backend (accept and send payments, webhook handling, monitoring): 3–5 weeks.







