Small shops that take bank transfers usually confirm them by hand: the bank app shows "deposit received", someone opens the order list and marks the order paid. Banks do offer APIs for this, but they come with contracts and fees that a small shop doesn't want.
The phone already gets the information for free, as a notification. So I wired the notification straight into code.
The setup
I used Droidline, an open source tool I've been building. It has two parts:
- an Android app that reads the screen and notifications through Android's accessibility and notification access, using only permissions an ordinary app can get
- a server on the PC (
droidline serve) that the app connects to over Wi-Fi
There's no ADB, no USB debugging and no root. You pair the phone once by scanning a QR code from droidline pair.
The code
The same two steps in every language: tell the phone which app's notifications to forward, then react to each one.
Python
from droidline import connect
d = connect("s23")
d.notify_filter(["com.example.bank"]) # only pass this app's notifications to the PC
def paid(n):
if n["text"].startswith("[Deposit]"):
mark_paid(n["text"]) # your own order system
d.on_notification(callback=paid)
input()
Node.js
import { connect } from "droidline";
const d = await connect("s23");
await d.notifyFilter(["com.example.bank"]);
await d.onNotification({ package: "com.example.bank" }, (n) => {
if (n.text.startsWith("[Deposit]")) markPaid(n.text);
});
C
using Droidline;
await using var d = await DroidlineClient.ConnectAsync("s23");
await d.NotifyFilterAsync(new[] { "com.example.bank" });
using var stop = await d.OnNotificationAsync(n =>
{
var text = n.Get<string>("text") ?? "";
if (text.StartsWith("[Deposit]")) MarkPaid(text);
}, package: "com.example.bank");
Console.ReadLine();
Shell
droidline notify_filter com.example.bank
droidline on_notification --package com.example.bank
The second command prints each notification as a JSON line until you stop it, so you can pipe it into anything.
notify_filter matters: by default nothing leaves the phone, and you choose which apps' notifications are forwarded. Replace com.example.bank with your bank app's package name, and the [Deposit] check with your bank's wording, since every bank phrases the notification differently. mark_paid stands for your own order system.
Install: pip install droidline, npm install droidline or dotnet add package Droidline. The droidline command and the Android app are on the releases page.
Things to know
- The phone has to stay on and connected. An old phone on a charger works fine for this.
- The connection is encrypted end to end (P-256 and AES-256-GCM), so the notification text never crosses the network in the clear.
- Android 15 and later hide one-time codes inside notifications from apps. Deposit notifications are not affected, but if you try the same trick for verification codes, it only works on Android 14 and earlier.
Beyond notifications
The same connection can tap, type, swipe, take screenshots and run OCR, and there's an MCP server so AI agents can use the phone. On a Galaxy S23 Ultra, finding an element by its text takes 16 ms (median of 10 runs), against 100 ms for Appium UiAutomator2 and 2,368 ms for adb plus uiautomator. The benchmark script is in the repo under scripts/bench.
- GitHub: https://github.com/KnifeLemon/Droidline (MIT)
- Docs: https://droidline.dev/docs/
I wrote this post with help from an AI assistant and checked every command against the project.
Top comments (5)
I'd keep mark_paid behind a reconciliation step rather than the text-prefix check alone. A useful fixture: two open orders for the same amount, one deposit notification, then the same notification delivered again after reconnect. Expect one unmatched payment candidate, not two paid orders. Where the bank exposes a transaction reference, preserve it separately from the notification ID; where it doesn't, flag the match for review. How does Droidline distinguish a newly received notification from an update or replay of an existing one?
Good fixture, and I agree
mark_paidshouldn't trust the text alone. The example kept matching short, but a real version should match amount and sender against open orders and send anything ambiguous to review, like your case of two orders and one deposit.On replays, every event the phone sends carries a sequence number, and the phone keeps it until the server acknowledges it. After a reconnect the phone resends only what came after the last acknowledged number, and the server drops any number it has already seen. While offline it keeps up to 500 notification events. So a dropped connection shouldn't turn one deposit into two events.
Your question also showed a gap, so 0.1.7 adds an
updatefield. It'struewhen the app changed a notification that was already showing andfalsewhen it's new, and thekeystays the same across updates. I checked it on a Galaxy S23 by posting the same notification twice, and the second event came withupdateset totrue. Code that should act once per deposit can skip updates. I'd still dedupe on a transaction reference parsed from the text when the bank includes one, kept apart from the notification key as you suggested.Cách tiếp cận này rất thực tế cho các shop nhỏ — đỡ tốn tiền thuê người ngồi chờ thông báo. Mấy lưu ý từ kinh nghiệm triển khai tương tự:
Xử lý encoding tiếng Việt: Notification content thường bị bẻ khi parse (dấu câu, emoji ngân hàng nhét vào). Nên normalize NFC trước khi regex.
Race condition: Nếu 2 giao dịch cùng lúc về,
NotificationListenerServicecó thể gom chung 1 batch. Cần correlation ID (thời gian + số tiền + 4 số cuối STK) để dedup.Battery optimization: Android 12+ kill background service khá gắt. Phải hướng user bật "Unrestricted battery" cho app companion, hoặc dùng
WorkManagerperiodic check fallback.Security: Đừng log raw notification content ra file/plaintext — có số tài khoản, tên người chuyển. Mask trước khi debug.
Fallback manual: Luôn có nút "Nhập tay" cho trường hợp app ngân hàng đổi format notif (Vietcombank/MB hay đổi UI silent update).
15 lines Python gọn nhẹ nhưng production hóa cần chăm chút mấy edge case này. Bạn có test với các ngân hàng VN chính (VCB, TCB, BIDV, MB, VPB) không? Format notif mỗi bên khác hẳn (site: labagent .tech)
Leaving an old phone on a charger for notification scraping works well until the OEM battery killer or notification bundling kicks in. When two deposits land within a few seconds, several banking apps collapse the individual alert text into an aggregated group summary, which hides the deposit amount in the extras bundle unless you traverse the child notifications explicitly.
Both are real. Droidline skips group summaries and forwards each child notification on its own, so when a bank app posts deposits as separate children under a summary, each amount arrives as its own event.
For apps that collapse several alerts into one notification with an inbox style list, 0.1.7 adds a
linesfield with each line, sincetextis often just the summary then. On a Galaxy S23, an inbox notification with the summary "2 new deposits" arrived with both deposit lines inlines.The battery side is still on the user. The app asks for unrestricted battery use during setup, and on Samsung phones I'd also keep it out of Sleeping apps.