Pattern
Gate vendor payments on compliance
The point of connecting VendMetric to your systems: stop paying vendors who have let a required document lapse. Here is the pattern, using the read API and webhooks.
The idea
Before your accounts-payable process releases a payment, it asks VendMetric one question: is this vendor compliant right now? If not, the payment is held and the right person is notified, until the vendor fixes what is missing. Removing that check later means changing a financial control, which is exactly the point.
Step 1: check at payment time
When a payment is about to be released, call the compliance endpoint for that vendor and read is_compliant.
async function vendorIsCompliant(vendorId) {
const res = await fetch(
`https://app.vendmetric.com/api/v1/vendors/${vendorId}/compliance`,
{ headers: { Authorization: `Bearer ${process.env.VENDMETRIC_KEY}` } },
);
if (!res.ok) return { ok: false, reason: "compliance check unavailable" };
const { data } = await res.json();
return {
ok: data.is_compliant,
reason: data.action_needed.map((r) => r.document_type_name).join(", "),
};
}
// In your payment release step:
const check = await vendorIsCompliant(vendor.vendmetricId);
if (!check.ok) {
holdPayment(payment, `Vendor not compliant: ${check.reason}`);
notifyProcurement(vendor, check.reason);
}Decide your own policy for the rare case where the check itself is unavailable (hold, or allow with a flag). VendMetric fails loudly with a non-200 rather than pretending a vendor is compliant.
Step 2: release the hold automatically
Rather than re-checking on a timer, subscribe to the vendor.compliance_changed webhook. When a held vendor becomes compliant again, release the payment.
// Your webhook receiver (verify the signature first, see the API reference)
app.post("/webhooks/vendmetric", (req, res) => {
if (!verify(req.rawBody, req.headers["x-vendmetric-signature"], SECRET)) {
return res.sendStatus(401);
}
const { event, data } = req.body;
if (event === "vendor.compliance_changed" && data.compliance === "COMPLIANT") {
releaseHeldPayments(data.vendor_id);
}
res.sendStatus(200);
});A worked example: QuickBooks Online
QuickBooks Online has no single field for "this bill is held." A Bill stays open until a separate BillPayment object links to it, so the hold is just which bills you choose to include the next time you run payments.
// QuickBooks Online has no single "hold" flag on a bill. A Bill stays
// open until a BillPayment links to it (BillPayment.Line[].LinkedTxn) --
// so the hold is simply which bills you choose to include.
async function payOpenBillsIfCompliant(vendorVendMetricId, qboVendorRef, openBills) {
const check = await vendorIsCompliant(vendorVendMetricId);
if (!check.ok) {
notifyProcurement(qboVendorRef, check.reason);
return; // leave every bill for this vendor unpaid; nothing to release yet
}
await fetch(`https://quickbooks.api.intuit.com/v3/company/${REALM_ID}/billpayment`, {
method: "POST",
headers: {
Authorization: `Bearer ${QBO_ACCESS_TOKEN}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
VendorRef: { value: qboVendorRef },
PayType: "Check",
TotalAmt: openBills.reduce((sum, b) => sum + b.Balance, 0),
Line: openBills.map((b) => ({
Amount: b.Balance,
LinkedTxn: [{ TxnId: b.Id, TxnType: "Bill" }],
})),
}),
});
}The same shape works for any AP platform with a distinct "pay" step separate from the bill itself: check compliance first, then only include that vendor's bills in the batch you submit.
What you get
- No payment leaves without a current compliance check.
- Holds clear themselves the moment the vendor fixes the gap.
- The whole decision is evidence: the check, the hold, and the release are all traceable to VendMetric's status at that moment.
Start with the API reference, or talk to us about your systems.
