Skip to content

Mercury

import { Aside } from ‘@astrojs/starlight/components’;

DPX integrates directly with Mercury — connecting Mercury’s banking layer to DPX’s settlement and oracle layer. When a Mercury payment is tagged for DPX routing, it flows automatically through oracle verification, AI synthesis, and on-chain settlement authorization.

Mercury transaction (ACH / wire / check)
→ DPX /mercury-webhook
→ Mercury-Signature HMAC-SHA256 verified
→ resourceType + operationType routing
→ Oracle check + AI synthesis layer
→ quoteId + execution params returned
→ Sender executes on-chain via router.settle()

Mercury handles the banking side (ACH, wires, balances). DPX handles the settlement intelligence side (oracle authorization, ESG scoring, on-chain execution params). DPX never holds or forwards funds — the caller executes settlement on-chain using the quoteId returned.

Use the Mercury API or dashboard to register the webhook:

Terminal window
curl -X POST https://api.mercury.com/api/v1/webhooks \
-H "Authorization: Bearer $MERCURY_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"url": "https://agent.untitledfinancial.com/mercury-webhook",
"eventTypes": ["transaction.created", "transaction.updated"]
}'

Copy the secretKey from the response — this is the webhook signing secret.

eventTypes is an array of strings. Omit the field entirely to receive all event types.

Terminal window
wrangler secret put MERCURY_WEBHOOK_SECRET
# Paste the secretKey from step 1
wrangler secret put MERCURY_API_KEY
# Your Mercury API key (Read-Write, IP-whitelisted)
wrangler secret put MERCURY_DEFAULT_RECIPIENT
# Fallback on-chain wallet address for dpx_route=true payments

Include a DPX tag in the Mercury payment note or metadata:

MethodExample
Note / memo"Intercompany Q2 dpx:0xYourWalletAddress"
Payment metadatadpx_recipient = 0xYourWalletAddress
Metadata flagdpx_route = true (uses MERCURY_DEFAULT_RECIPIENT)

Payments without a DPX tag are acknowledged and ignored — Mercury receives 200 OK with action: ignored.

Mercury sends Mercury-Signature (title-case, single header) on every webhook delivery.

Format: t=<unix_timestamp_seconds>,v1=<hmac_hex>

Signing algorithm: HMAC-SHA256 of "<timestamp>.<rawBody>" (literal period separator between timestamp and raw request body).

const header = request.headers.get('Mercury-Signature');
const parts = Object.fromEntries(header.split(',').map(p => p.split('=')));
const { t: timestamp, v1: expected } = parts;
// Reject replays older than 5 minutes
if (Math.abs(Date.now() / 1000 - Number(timestamp)) > 300) throw new Error('Stale');
const signedMsg = `${timestamp}.${rawBody}`; // period separator

Mercury webhooks use JSON Merge Patch format. There is no eventType field and no data wrapper.

{
id: string; // webhook delivery ID
resourceType: string; // "transaction" | "checkingAccount" | ...
resourceId: string; // ID of the affected resource
operationType: string; // "create" | "update"
resourceVersion: number;
occurredAt: string;
changedPaths: string[];
mergePatch: Record<string, unknown>; // new field values (partial)
previousValues: Record<string, unknown>; // old field values (partial)
}

Route on resourceType + operationType. Transaction data is in mergePatch, not under a data key. For transaction.update events, DPX only routes when mergePatch.status === "sent" to avoid double-routing on pending.

Amount in mergePatch.amount is in dollars — Mercury never sends cents.

The Settlement Agent includes a Mercury payment proxy at /mercury/send. All payments require a saved recipientId — Mercury’s /transactions endpoint does not accept inline routing numbers.

Terminal window
# ACH recipient
POST https://agent.untitledfinancial.com/mercury/recipients
{
"name": "Acme Corp",
"emails": ["[email protected]"],
"electronicRoutingInfo": {
"routingNumber": "021000021",
"accountNumber": "123456789",
"electronicAccountType": "businessChecking",
"address": {
"address1": "123 Main St",
"city": "New York",
"region": "NY",
"postalCode": "10001",
"country": "US"
}
}
}
Terminal window
# International wire recipient
POST https://agent.untitledfinancial.com/mercury/recipients
{
"name": "Foreign Vendor Ltd",
"emails": ["[email protected]"],
"defaultPaymentMethod": "internationalWire",
"internationalWireRoutingInfo": {
"iban": "DE89370400440532013000",
"swiftCode": "COBADEFFXXX",
"bankDetails": {
"bankName": "Commerzbank",
"bankCityState": "Frankfurt",
"bankCountry": "DE"
}
}
}

Note: emails is an array of strings. electronicRoutingInfo is the field name for ACH (not top-level routingNumber/accountNumber).

Terminal window
POST https://agent.untitledfinancial.com/mercury/send
{
"accountId": "your-mercury-account-uuid",
"recipientId": "saved-recipient-uuid",
"amount": 15000.00,
"paymentMethod": "ach",
"note": "Q2 vendor payment dpx:0xYourWallet",
"idempotencyKey": "invoice-2026-q2-001"
}

Payment methods on /transactions: ach, domesticWire, check

domesticWire requires a purpose field:

{
"purpose": { "category": "Vendor" }
}

Valid categories: Employee, Landlord, Vendor, Contractor, Subsidiary, TransferToMyExternalAccount, FamilyMemberOrFriend, ForGoodsOrServices, AngelInvestment, SavingsOrInvestments, Expenses, Travel, Other

Vendor, Contractor, and Other also require additionalInfo (the name or description).

International wire routes through /request-send-money (admin approval flow) and is not a valid paymentMethod on /transactions:

{
"paymentMethod": "internationalWire",
"recipientId": "uuid-with-internationalWireRoutingInfo"
}

Amount is in dollars. Minimum $0.01. idempotencyKey is in the request body (not a header). Auto-generated if omitted.

To route a Mercury USD payment to a EUR settlement, add destination_currency to the payment metadata:

destination_currency = EUR

DPX applies the FX fee and routes through EURC on Base.

Before executing an ACH payment, run it through the DPX compliance oracle:

Terminal window
POST https://agent.untitledfinancial.com/mercury/ach-authorize
{
"recipientId": "mercury-recipient-uuid",
"amount": 15000.00,
"accountName": "Acme Corp",
"routingNumber": "021000021",
"accountNumber": "123456789",
"memo": "Q2 vendor payment"
}

Response:

{
"decision": "APPROVED",
"requiresReview": false,
"autoExecute": true,
"compliance": {
"sanctionsClean": true,
"anomalyScore": 0.12,
"riskTier": "LOW"
}
}
DecisionHTTPMeaning
APPROVED + autoExecute: true200ACH fires immediately
FLAGGED + requiresReview: true200Queued for manual review
BLOCKED403Hard stop — do not proceed

Four Mercury tools are available in the MCP server:

ToolWhat it does
mercury.accountsList all Mercury accounts with live balances
mercury.transactionsList recent transactions for an account
mercury.sendInitiate a payment — ACH, wire, or international wire — with optional DPX routing
mercury.ach_authorizeCompliance pre-screening via DPX AML oracle before ACH execution

When mercury.send is called with dpxRoute: true, the payment is tagged for DPX settlement. The Settlement Agent picks it up via webhook, runs oracle verification, and returns execution params for on-chain settlement.

Example agent flow:

User: "Pay $50,000 to our EU supplier via DPX"
Agent calls:
1. mercury.accounts → finds available balance
2. oracle.stability → confirms STABLE conditions
3. settlement.quote → binding fee quote, 300s TTL
4. mercury.send → initiates wire tagged dpx:0x...
→ DPX webhook → oracle + AI synthesis → quoteId returned
→ caller executes router.settle() on Base

If the oracle returns CAUTION or UNSTABLE, the AI synthesis layer will HOLD:

{
"settlementId": "dpx_abc123",
"status": "held",
"reasoning": "CAUTION conditions with $500,000 exceeds $100K threshold. Flagging for review.",
"oracleScore": 81
}

The Mercury payment is already sent — DPX logs the hold. Resubmit when oracle returns STABLE, using the same referenceId.

VariableRequiredDescription
MERCURY_WEBHOOK_SECRETYes (webhook)secretKey returned by Mercury when registering the webhook endpoint
MERCURY_API_KEYYes (proxy/watcher)Mercury API key — Read-Write, IP-whitelisted for Cloudflare Workers IPs
MERCURY_DEFAULT_RECIPIENTRecommendedFallback on-chain address for dpx_route=true payments