Live demo: https://arkuss.app (PayPal sandbox). Sandbox only, and not insurance.

Inspiration

When an earthquake hits, the people who need money first are the ones who can least wait for a claims process. Indonesia sits on the Ring of Fire, and many families depend on relatives abroad who cannot easily help from a distance. A parametric design removes the loss adjuster: if a public measurement (magnitude and distance) crosses a threshold, a fixed amount is paid. That needs three things PayPal already does well: collect small recurring premiums from anywhere, move money to an e-mail address in minutes, and tell the system about every event through signed webhooks.

What it does

Protect: a customer, or a relative abroad, subscribes for US$2 per month with PayPal Subscriptions and names a beneficiary and an area. Watch: an engine polls the worldwide USGS feed and has every quake confirmed by a second, independent agency: EMSC anywhere in the world, plus BMKG inside Indonesia. A quake with no second source is held for a person, never paid on a single feed. Real quakes are judged only after USGS has settled the magnitude, and a judged quake is final. Show: a live worldwide map plots real recent earthquakes from USGS (size by magnitude, colour by depth), with BMKG's detail and official shakemap for Indonesia, next to the areas ARKUS protects and the quakes its rules have judged. It is display only and cached; the payout engine never reads it. Decide, deterministically: a public rule table turns magnitude and distance into a payout (M6.0+ within 50 km: US$100; M7.0+ within 150 km: US$300, illustrative demo tiers). Cover needs an active policy, a paid premium and a finished waiting period, so nobody can buy cover after a quake. One paid premium buys one payout: once a claim is paid, the cover is used until the next monthly premium. Pay: PayPal Payouts sends the money. The payout id is derived from policy and quake, so a retry can never pay twice. Large payouts, feed disagreement, a nearly empty reserve or a daily limit send the claim to a person; a kill switch stops every payout. Verify with a bounded agent: when a claim is held only because the second-source check was not clean, a verification agent gathers evidence and a model judges one question, "do these records describe the same earthquake?". Code, not the model, decides whether that answer may release the claim, and only inside a hard envelope. It never touches an amount. Your own page: after PayPal approval the customer lands on /policy/: status, the public rules, every claim with its memo and the agent's evidence, and a button that cancels the PayPal subscription. In demo mode a judge can also simulate a quake near their own policy and watch the whole loop without any operator access. Explain with AI: an agent writes a plain-language decision memo in English and Indonesian, flags anomalies and advises APPROVE / REVIEW / REJECT. A chat assistant answers questions in both languages. Prove: every action lands in a public, hash-chained audit log that anyone can re-verify, so history cannot be edited silently. It is a parametric protection pilot, not insurance, and it refuses to start against any non-sandbox PayPal host. How AI is used, and where it is not AI never sets or changes an amount. The rule table decides how much; the only thing a model may influence is whether a claim held only for missing second-source evidence is released, and only through hard limits in code.

The amount and the eligibility come from the rule table only. The verification agent (src/verifier.ts) sees numbers only: the USGS event detail, what EMSC and BMKG list nearby (time offset, distance, magnitude gap) and other USGS events. A claim is released only if there is no other hold, the model says CONFIRM with high confidence, USGS has reviewed the event, an independent agency record sits inside a 30 min / 300 km / 0.7 magnitude envelope, and the daily agent limit is not reached. Any doubt escalates to a person. A verdict that mentions money is discarded. Every verdict is stored with its evidence and sha256 hash, shown on the claim, and written to the audit log; the claim records who decided (rules, AI agent or operator). The agent's memo is advice: it cannot change a claim's status or amount. Every currency figure in a memo must be one the rules produced (this claim, a tier, the premium or a limit); otherwise the memo is discarded and a deterministic template is used. The check understands USD 300, US$ 300,00, 300 dollars and rejects any rupiah figure. Anomaly flags are computed in code (simulated event, feed disagreement, quake not yet reviewed, cover that started hours before the quake, a quake near a tier boundary, many claims from one quake); the model explains them and may add observations. The model gets facts only: no e-mail address, no exact coordinates. The memo is stored once per claim with its sha256 hash in the audit log. The chat assistant has no tools and no database access. The server builds a small public-safe context (rules, reserve, recent quakes, and a policy's public status only when its id is pasted). Asking it to approve a claim or reveal a secret does nothing. Without a model, or when the model's answer is empty, filtered, not valid JSON or ungrounded, the system falls back to the template and the payout is never delayed. The model layer is provider-neutral (any OpenAI-compatible endpoint); the demo ran on deepseek-v4.1-flash through 9inference.cloud.

How we built it

Node.js 20 / TypeScript, Express 5, SQLite (better-sqlite3), zod, vitest. No framework on the front end: one static dashboard with a vendored Leaflet map and AG Grid tables (audit trail, earthquake list) that sort and filter.

PayPal pieces: product, plan and subscription creation (/v1/catalogs/products, /v1/billing/plans, /v1/billing/subscriptions); webhook signature verification with /v1/notifications/verify-webhook-signature (fail closed); Payouts (/v1/payments/payouts and batch lookup) with PayPal-Request-Id and a deterministic sender_batch_id.

Design rules we kept: sandbox only (assertSandbox with an exact host allow-list); money in integer cents; every state change in a transaction together with its audit row; webhooks applied idempotently by event id and by sale id; a policy state machine where cancelled and expired are terminal.

Challenges we ran into

Webhooks do not arrive in order, and PayPal retries. A payment can arrive before its subscription is linked. We refuse to mark such an event as seen and answer 503 so PayPal redelivers it; a PayPal outage during signature verification also answers 503, never 401. Never pay twice. The reserve is debited in the same transaction that marks the claim as submitting; a timeout leaves it submitting and a retry reuses the same ids; one attempt per claim runs at a time. A test caught a real race: three concurrent attempts produced three PayPal requests before we added the per-claim lock. Stale data. Querying USGS only at the trigger magnitude would hide a downward revision and leave us paying on an outdated value, so the feed is queried half a magnitude lower. A guard that was too weak. The first version of the "AI must not invent amounts" check missed USD 5000 (prefix form) and misread Indonesian decimals (300,00). Live testing with a real model exposed it; both are fixed and tested. Real feeds are messier than fixtures. Live data found a money bug no fixture had: USGS listed one real quake twice under two ids, which would have paid two claims. Claims for the same policy are now merged when two real listings are within 5 minutes and 100 km. EMSC also lagged USGS by many minutes, so a quake judged NOT_FOUND was in fact confirmed later; unconfirmed quakes are now retried for an hour before they are judged. Giving an agent authority safely. We let a model release evidence-only holds, but never trust it: the code gate checks every other hold, a hard evidence envelope and a daily limit before the model's opinion counts, and we mutation-tested the gate by weakening each check and confirming a test fails. Reasoning models and structured output. A reasoning model sometimes spent its token budget thinking and returned nothing. Disabling its reasoning, a larger budget and one retry made 8 of 8 live memos succeed in about three seconds each.

Accomplishments that we're proud of

203 automated tests, with the critical ones mutation-checked (we broke the code on purpose and confirmed a test fails). Verified on the live PayPal sandbox: signed webhooks, subscription lifecycle, duplicate handling, a US$100 automatic payout and a US$300 payout after manual approval, both settled. A public audit chain that detects tampering, and an AI layer that cannot touch an amount.

What we learned

Let AI do what it is good at (explaining, flagging, translating) and keep arithmetic and authority in boring, tested code. Treat every model output as untrusted input, and write the failure path first.

What's next for ARKUS Guard

A licensed insurance partner and regulatory approval (OJK in Indonesia) before any real money; real quake judging exercised against a qualifying live event; PayPal payout webhooks in addition to read-back; exercising the verification agent against a real held claim; per-beneficiary identity checks; a second independent measurement source; a coarser public map grid.

Built With

Share this project:

Updates

Submission history