Skip to content

Self-test — the required final step before going live

Before you enable your fulfillment endpoint for live agent traffic, run this self-test. Sill will run its own hookup checks at the moment you save your endpoint URL, but there is one check Sill CANNOT do from the outside: verify that a request for a real order id and the WRONG customer email refuses to return the order. That check requires your customer database — nobody but you can decide what “wrong email” means.

Everything below runs on your machine. Sill never sees your test order, your customer’s email, or your endpoint’s response bytes.

When you save an endpoint URL in the dashboard, Sill runs three checks before the skill starts advertising to agents:

  1. Reachability — Sill sends a GET to your endpoint and expects any HTTP 2xx, 3xx, or 4xx. A network error or a 5xx blocks the skill.
  2. Signature enforcement — Sill sends two POST requests to your endpoint using a canonical, PII-free probe body: one with the X-Sill-Signature header OMITTED, one with a well-formed but wrong-secret HMAC. Your endpoint MUST reject both with a non-2xx. If either is accepted, any caller who can reach your URL can forge a call to your handler, and the skill is blocked.
  3. Response shape — Sill sends one correctly-signed probe with a marker order id that does not exist in your database, and expects a response that validates against the canonical shape on the order_status page (and the sibling pages for the other four read skills you have attached). An over-sharing shape — say, a customer_address field on order_status — is caught here, before real customer data flows.

These three checks catch the loudest, most common integration mistakes. They do NOT verify that your handler correctly refuses a real order id when the customer email does not match — that check needs real data. This page tells you how to run it yourself.

Sill has no way to know which email owns which order in your database, so the following list is on you. Each item is a common way an otherwise-correct handler still leaks data. Run every check locally with real order data before you go live.

  1. Real order id, WRONG customer email → not the order. This is the customer-scoping check (BOLA). Take a real order id in your database and pair it with any email that does NOT own it. Your handler MUST refuse. The recommended response for order_status and track_shipment is HTTP 404 with an empty body.
  2. Real order id, RIGHT customer email → the canonical shape ONLY. Return the fields the canonical response schema lists and nothing else. Do NOT include the customer’s name, shipping address, phone, internal notes, payment method, or any other field. Sill’s response validator will reject over-shares with additionalProperties: false, but you should catch them at the source.
  3. Sequential order ids don’t leak existence. A caller who guesses #1001, #1002, #1003 should see the same response shape whether or not each id exists. Do NOT distinguish “order not found” from “wrong customer” — both should be indistinguishable. The recommended answer for both is HTTP 404 with an empty body.
  4. Wrong email → not-found response takes similar time as a real-email response. A handler that returns instantly on wrong-email and takes 200ms on right-email leaks existence through a timing side channel. Compare the argument email to the stored customer email in constant time (crypto.timingSafeEqual in Node, hmac.compare_digest in Python, hmac.Equal in Go).
  5. One email with multiple orders returns the RIGHT one. If a customer has multiple orders in your database, a call for order_id: A, customer_email: X where X also owns B and C should return only A. Do not aggregate.
  6. The signed request’s timestamp is enforced. Reject a correctly-signed request whose t value is more than about five minutes off from your server’s clock. The contract overview has the exact snippet.

Use this to make ONE correctly-signed request against your endpoint by hand. Substitute the secret and the endpoint URL from your dashboard, and the order id + email from your test data.

Terminal window
SECRET='whs_...paste_from_dashboard...'
ENDPOINT='https://shop.example.com/sill/skills'
BODY='{"skill_id":"order_status","site_id":"01K00000000000000000000000","arguments":{"order_id":"#1001","customer_email":"buyer@example.com"},"observed_at":"2026-07-07T00:00:00.000Z","nonce":"selftest0000000000000000000000"}'
T=$(date +%s)
SIG=$(printf '%s.%s' "$T" "$BODY" | openssl dgst -sha256 -hmac "$SECRET" -hex | awk '{print $NF}')
curl -sSi -X POST "$ENDPOINT" \
-H 'content-type: application/json' \
-H "x-sill-signature: t=$T,v1=$SIG" \
--data "$BODY"

To exercise the wrong-email path, change the customer_email in $BODY to any email that does NOT own #1001 in your database, recompute $SIG, and re-run. The response should be HTTP 404 with an empty body.

Automated check — sill-endpoint-check.mjs

Section titled “Automated check — sill-endpoint-check.mjs”

Sill publishes a single-file Node script that runs the same three hookup checks against your endpoint AND the customer-scoping check that only you can meaningfully run. Zero npm dependencies, Node 18+ built-ins only, works offline.

Terminal window
curl -sSLo sill-endpoint-check.mjs https://docs.sill.so/skills/v1/sill-endpoint-check.mjs
SILL_ENDPOINT_URL="https://shop.example.com/sill/skills" \
SILL_SKILL_SECRET="whs_...paste_from_dashboard..." \
SILL_TEST_ORDER_ID="#1001" \
SILL_TEST_ORDER_EMAIL="buyer@example.com" \
SILL_TEST_WRONG_EMAIL="notmine@example.com" \
node sill-endpoint-check.mjs
VariableNotes
SILL_ENDPOINT_URLYour HTTPS fulfillment endpoint — the URL you registered in the dashboard for order_status. http://localhost:... is accepted for testing against a local staging instance.
SILL_SKILL_SECRETThe shared secret Sill showed you once when you saved the skill. It starts with whs_.
SILL_TEST_ORDER_IDA real order id in your database. Test-mode or sandbox orders are fine. The script sends it twice — once with the right email, once with a wrong one.
SILL_TEST_ORDER_EMAILThe customer email that owns SILL_TEST_ORDER_ID in your database.
SILL_TEST_WRONG_EMAILAny email that does NOT own SILL_TEST_ORDER_ID. The customer-scoping check pairs this with the real order id.
SILL_SITE_IDOptional. Your site’s ULID. Defaults to a placeholder. If your handler ignores site_id you can leave this unset.
SILL_REQUEST_TIMEOUT_MSOptional. Per-request timeout. Defaults to 10000.
  1. ReachabilityGET to your endpoint returns any 2xx / 3xx / 4xx.
  2. Signature: unsigned request — a POST with the X-Sill-Signature header omitted is REJECTED (any non-2xx).
  3. Signature: wrong-secret request — a POST with a well-formed but wrong-key HMAC is REJECTED.
  4. Happy path — a correctly-signed POST with your real order id and right email returns HTTP 200 and a body that matches the canonical order_status response schema (additionalProperties: false at every depth).
  5. Customer scoping — a correctly-signed POST with your real order id and the WRONG email does NOT return the order. The script prints a loud [FAIL] if the response body contains line items for a wrong-email request — that is a customer-scoping leak (BOLA).

Each check prints a single line: [PASS], [WARN], or [FAIL], the check name, and a short reason. The script never prints your secret and never prints your endpoint’s raw response body.

  • [PASS] — the check passed.
  • [WARN] — the check surfaced something recommended-to-fix but not a hard block. The most common [WARN] is a wrong-email response that returns HTTP 200 with an empty body instead of HTTP 404 with an empty body — no data was leaked, but a distinguishable “wrong email” response tells the caller the order id exists.
  • [FAIL] — a check that must be fixed before going live. The script exits non-zero.

Exit codes:

  • 0 — every check passed or only produced warnings.
  • 1 — at least one check failed.
  • 2 — the configuration is missing or invalid.

The check the script can’t do without your help

Section titled “The check the script can’t do without your help”

The customer-scoping check depends on SILL_TEST_WRONG_EMAIL genuinely NOT being the owner of SILL_TEST_ORDER_ID in your database. If you accidentally set it to another email that DOES own the order (an alt email on the same customer, for example), the check might pass on a leaky endpoint. Pick a wrong email that no customer in your database uses.

You are ready to go live. The dashboard’s skill row will show the results of Sill’s own hookup checks — the ones you already saw in this script’s first three lines. The customer-scoping check is the one Sill cannot re-run: it stays your responsibility on every code change to your handler.

If you change your handler, re-run this self-test. It takes about five seconds and it is the cheapest way to catch a regression before real agent traffic hits your endpoint.

  • Contract overview — envelope shape, HMAC signing, and full verification samples in TypeScript / Python / Go.
  • order_status — the canonical response shape the script validates against.
  • track_shipment — the other customer-scoped skill. Same discipline; re-run the checklist above with a track_shipment request body.