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
| Type | Expected fields | Notes |
|---|---|---|
| stripe.refund | payment, amount?, currency? | Amounts in minor units. Detects wrong amounts, duplicates, pending and failed refunds. |
| stripe.subscription_cancelled | subscription | Scheduled-at-period-end is FAILED — the subscription is still active. |
| stripe.payment_succeeded | payment, amount?, currency? | Checks amount_received, not intent. |
| github.pr_merged | repository, pull_request, commit? | Closed-without-merge is FAILED. Optional merge-commit assertion. |
| github.workflow_passed | repository, run, commit? | Queued or in-progress runs are UNKNOWN, not failed. |
| github.issue_closed | repository, issue | A number that points at a PR fails with NOT_AN_ISSUE. |
| email.delivered | email, 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.