Matrix and lint
Lint rules
Every integration lint rule.
Every danwo test and danwo matrix run is linted over its provider calls and webhooks. Findings print after the run, go into flight records, GitHub annotations and JUnit, and do not fail the run unless config lint.failOn says so. Set a rule's level with lint.rules; danwo lint re-runs the rules over a recorded run.
| Name | Kind | Description |
|---|---|---|
webhooks/accepts-forged | error | any — The app accepted a webhook signed with the wrong secret (seen in danwo matrix trials). Verify every webhook's signature with the provider's signing secret before acting on it, and answer 400 when it does not match. |
payments/unconfirmed | error | stripe, paystack, flutterwave, bachs — The app gave value after a hosted payment without confirming it (no verify call, no webhook): it trusted the redirect. Fulfil from the provider's signed webhook, or verify the payment with the provider's API on the redirect, before granting anything. |
resilience/duplicate-create | error | stripe, bachs — A create was retried after its answer was lost, without the first call's Idempotency-Key. Reuse the same Idempotency-Key when you retry a call, so the provider returns the first result instead of creating a second one. |
secrets/key-in-browser | error | any — The browser sent a server-only key (a Stripe or Paystack secret key, Flutterwave secret, Slack or GitHub token, Twilio credentials). Call the provider from your server with the secret key; give the browser only a publishable key, or a short-lived token your server mints. |
secrets/key-in-url | error | any — A server-only key was sent in a URL query string. Send keys in the Authorization header; URLs end up in logs, proxies and browser history. |
stripe/idempotency-key | warning | stripe — A Stripe call that moves money was sent without an Idempotency-Key header. Send a key derived from your own order or attempt id, so a retried request cannot charge, refund or pay out twice. |
flutterwave/verify-transaction | warning | flutterwave — The app gave value for a Flutterwave payment without verifying the transaction by its API. Call GET /v3/transactions/:id/verify and check its status, amount and currency before giving value, even when a webhook arrived. |
webhooks/accepts-stale | warning | stripe, slack, resend, bachs, google, composio — The app accepted a webhook whose signed timestamp was too old to be fresh. Check the signature's timestamp against a tolerance (Stripe's libraries use 5 minutes), so a captured delivery cannot be replayed later. |
webhooks/rejects-valid | warning | any — The app refused a correctly signed webhook (any 4xx, or a 5xx in a run without faults). Answer 2xx to deliveries you have stored, including duplicates you skip; reserve 4xx for bad signatures, since providers do not retry 4xx answers. |
webhooks/slow-answer | warning | any — The app took longer to answer a webhook than the provider waits. Answer 2xx as soon as the delivery is stored, and do the slow work afterwards, so the provider does not retry a delivery you handled. |
resilience/retry-after-ignored | warning | any — A call was retried sooner than its 429 answer's Retry-After allowed. Wait at least Retry-After seconds before retrying a rate-limited call; retrying sooner prolongs the limit. |
oauth/missing-state | warning | google, microsoft, github, slack — A sign-in started without a state parameter, so a forged callback cannot be told apart. Send a random state with every authorization request and check it on the callback before exchanging the code. |
messages/duplicate-send | warning | resend, twilio, google, microsoft, slack — The same email, text or chat message was sent twice for one action. Send once per trigger: dedupe on the event or job id that caused the message, and make retries reuse the first send's id. |
resilience/no-retry-on-read | info | any — A provider read failed with 429, 503 or a dropped connection and was not tried again. Retry reads that fail transiently, with backoff and a cap, before showing an error. |
oauth/missing-pkce | info | google, microsoft, github — A sign-in started without PKCE (no code_challenge). Send a code_challenge (S256) and its verifier when exchanging the code, so a stolen code is useless on its own. |
secrets/publishable-on-server | info | stripe, paystack, flutterwave — The app's server called a provider with a publishable key instead of its secret key. Publishable keys are for the browser; server calls should use the secret key from your server's environment. |
data/no-retry-on-serialization | warning | any — A Postgres statement failed with serialization_failure (40001) or deadlock_detected (40P01) and its transaction was not run again. Roll back and run the whole transaction again, with a few tries and backoff: Postgres asks for exactly that with these two errors. |