Documentation

DidWork verifies that software actually did what it says it did. You send a claim; DidWork independently gathers evidence from the systems that can prove the outcome and returns a verdict. The API lives at https://api.didwork.sh.

Quickstart

Get an API key from the console, then:

# TypeScript
npm install @didwork/sdk
import { did } from "@didwork/sdk";
// reads DIDWORK_API_KEY from the environment

const result = await did.verify({
  type: "stripe.refund",
  expected: { payment: "pi_123", amount: 4999 }
});
# Python
pip install didwork
from didwork import did

verification = did.verify(
    type="stripe.refund",
    expected={"payment": "pi_123", "amount": 4999},
)

Authentication

Every request sends a bearer key. Keys are shown once at creation; only a hash is stored.

Authorization: Bearer dk_...

The SDKs read DIDWORK_API_KEY (and optionally DIDWORK_BASE_URL) from the environment, or accept explicit configuration.

Verify

POST https://api.didwork.sh/v1/verify

{
  "type": "stripe.refund",
  "expected": { "payment": "pi_123", "amount": 4999 },
  "subject": "order_18427"   // optional caller-side reference
}

Response:

{
  "verification_id": "vrf_829...",
  "status": "verified",
  "claim": { ... },
  "evidence": [
    { "source": "stripe", "observation": "1 refund(s) found for pi_123", "data": [...] }
  ],
  "created_at": "...",
  "verified_at": "..."
}

On failure the verdict carries a machine-readable reason plus expected / observed where applicable — e.g. AMOUNT_MISMATCH, PR_CLOSED_NOT_MERGED, RECIPIENT_MISMATCH.

Verdicts

Every verification ends in one of three states:

  • VERIFIED — the evidence sufficiently proves the claim.
  • FAILED — the evidence shows the claim is not true.
  • UNKNOWN — the outcome can't be established: provider unreachable, record not visible, or not yet decidable (a pending refund, an in-progress workflow run).

UNKNOWN is deliberate. DidWork prefers uncertainty over false verification. Treat UNKNOWN as "do not proceed yet" — retry, or route to a human.

Claim types

TypeExpected fieldsNotes
stripe.refundpayment, amount?, currency?Amounts in minor units. Detects wrong amounts, duplicates, pending and failed refunds.
stripe.subscription_cancelledsubscriptionScheduled-at-period-end is FAILED — the subscription is still active.
stripe.payment_succeededpayment, amount?, currency?Checks amount_received, not intent.
github.pr_mergedrepository, pull_request, commit?Closed-without-merge is FAILED. Optional merge-commit assertion.
github.workflow_passedrepository, run, commit?Queued or in-progress runs are UNKNOWN, not failed.
github.issue_closedrepository, issueA number that points at a PR fails with NOT_AN_ISSUE.
email.deliveredemail, to?Provider acceptance is not delivery — sent/queued states are UNKNOWN until a terminal event.

Async & webhooks

Some outcomes take time. Pass "wait": false to get a pending verification immediately:

{
  "type": "email.delivered",
  "expected": { "email": "..." },
  "wait": false,
  "webhook_url": "https://your.app/hooks/didwork"
}

Poll GET /v1/verifications/:id, or receive a verification.completed event at your webhook URL. Events are signed: the didwork-signature header is sha256=HMAC_SHA256(webhook_secret, body), using the webhook secret issued with your API key. Verify it before trusting the payload.

Log & usage

GET /v1/verifications?limit=20   # recent verifications for your key
GET /v1/verifications/:id        # one verification, evidence included
GET /v1/usage                    # verifications performed, by month

Provider permissions

DidWork reads authoritative state — it never needs write access to your providers. Execution credentials aren't evidence credentials: a key that can only cause actions can't prove them.

  • Stripe — a restricted key with read access to refunds, payment intents, and subscriptions is sufficient.
  • GitHub — a fine-grained token with read access to pull requests, issues, and actions on the repositories you verify.
  • Resend — a full-access key: sending-only keys cannot read message state back.