{"templateId":"markdown","sharedDataIds":{"sidebar":"sidebar-payments/sidebars.yaml"},"props":{"metadata":{"markdoc":{"tagList":[]},"type":"markdown"},"seo":{"title":"Tracking & webhooks","description":"Start today using our public API to power your next project."},"dynamicMarkdocComponents":[],"compilationErrors":[],"ast":{"$$mdtype":"Tag","name":"article","attributes":{},"children":[{"$$mdtype":"Tag","name":"Heading","attributes":{"level":1,"id":"tracking--webhooks"},"children":["Tracking & webhooks"]},{"$$mdtype":"Tag","name":"blockquote","attributes":{},"children":[{"$$mdtype":"Tag","name":"p","attributes":{},"children":["Webhooks tell you when something changed; ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["GET /v1/payments/{id}"]}," tells you what is true now."]}]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":2,"id":"the-lifecycle"},"children":["The lifecycle"]},{"$$mdtype":"Tag","name":"CodeBlock","attributes":{"data-language":"text","header":{"controls":{"copy":{}}},"source":"accepted ──► in-review ──► in-progress ──► completed ──► refunded\n    │            │              │\n    ▼            ▼              ▼\n rejected     canceled        failed\n\nscheduled ──► in-progress ──► …\n    │\n    ▼\n canceled\n","lang":"text"},"children":[]},{"$$mdtype":"Tag","name":"div","attributes":{"className":"md-table-wrapper"},"children":[{"$$mdtype":"Tag","name":"table","attributes":{"className":"md"},"children":[{"$$mdtype":"Tag","name":"thead","attributes":{},"children":[{"$$mdtype":"Tag","name":"tr","attributes":{},"children":[{"$$mdtype":"Tag","name":"th","attributes":{"align":"left","data-label":"Status"},"children":["Status"]},{"$$mdtype":"Tag","name":"th","attributes":{"align":"left","data-label":"Meaning"},"children":["Meaning"]}]}]},{"$$mdtype":"Tag","name":"tbody","attributes":{},"children":[{"$$mdtype":"Tag","name":"tr","attributes":{},"children":[{"$$mdtype":"Tag","name":"td","attributes":{"align":"left"},"children":[{"$$mdtype":"Tag","name":"code","attributes":{},"children":["accepted"]}]},{"$$mdtype":"Tag","name":"td","attributes":{"align":"left"},"children":["Passed all validations and accepted for processing."]}]},{"$$mdtype":"Tag","name":"tr","attributes":{},"children":[{"$$mdtype":"Tag","name":"td","attributes":{"align":"left"},"children":[{"$$mdtype":"Tag","name":"code","attributes":{},"children":["in-review"]}]},{"$$mdtype":"Tag","name":"td","attributes":{"align":"left"},"children":["Held for compliance or manual review before release."]}]},{"$$mdtype":"Tag","name":"tr","attributes":{},"children":[{"$$mdtype":"Tag","name":"td","attributes":{"align":"left"},"children":[{"$$mdtype":"Tag","name":"code","attributes":{},"children":["scheduled"]}]},{"$$mdtype":"Tag","name":"td","attributes":{"align":"left"},"children":["Accepted and queued for a future execution date."]}]},{"$$mdtype":"Tag","name":"tr","attributes":{},"children":[{"$$mdtype":"Tag","name":"td","attributes":{"align":"left"},"children":[{"$$mdtype":"Tag","name":"code","attributes":{},"children":["in-progress"]}]},{"$$mdtype":"Tag","name":"td","attributes":{"align":"left"},"children":["Released for processing; funds are moving."]}]},{"$$mdtype":"Tag","name":"tr","attributes":{},"children":[{"$$mdtype":"Tag","name":"td","attributes":{"align":"left"},"children":[{"$$mdtype":"Tag","name":"code","attributes":{},"children":["completed"]}]},{"$$mdtype":"Tag","name":"td","attributes":{"align":"left"},"children":["Funds delivered to the beneficiary."]}]},{"$$mdtype":"Tag","name":"tr","attributes":{},"children":[{"$$mdtype":"Tag","name":"td","attributes":{"align":"left"},"children":[{"$$mdtype":"Tag","name":"code","attributes":{},"children":["rejected"]}]},{"$$mdtype":"Tag","name":"td","attributes":{"align":"left"},"children":[{"$$mdtype":"Tag","name":"strong","attributes":{},"children":["Terminal."]}," Declined before processing; no funds moved."]}]},{"$$mdtype":"Tag","name":"tr","attributes":{},"children":[{"$$mdtype":"Tag","name":"td","attributes":{"align":"left"},"children":[{"$$mdtype":"Tag","name":"code","attributes":{},"children":["failed"]}]},{"$$mdtype":"Tag","name":"td","attributes":{"align":"left"},"children":[{"$$mdtype":"Tag","name":"strong","attributes":{},"children":["Terminal."]}," Processing started but could not complete; funds returned."]}]},{"$$mdtype":"Tag","name":"tr","attributes":{},"children":[{"$$mdtype":"Tag","name":"td","attributes":{"align":"left"},"children":[{"$$mdtype":"Tag","name":"code","attributes":{},"children":["canceled"]}]},{"$$mdtype":"Tag","name":"td","attributes":{"align":"left"},"children":[{"$$mdtype":"Tag","name":"strong","attributes":{},"children":["Terminal."]}," Stopped while held; no funds moved. You can cancel a payment yourself while it is ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["in-review"]}," or ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["scheduled"]}," (see ",{"$$mdtype":"Tag","name":"a","attributes":{"href":"/payments/payment-flow/scheduled-payments#cancelling-a-payment"},"children":["Cancelling a payment"]},"); the ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["canceled"]}," webhook is sent when you do."]}]},{"$$mdtype":"Tag","name":"tr","attributes":{},"children":[{"$$mdtype":"Tag","name":"td","attributes":{"align":"left"},"children":[{"$$mdtype":"Tag","name":"code","attributes":{},"children":["refunded"]}]},{"$$mdtype":"Tag","name":"td","attributes":{"align":"left"},"children":[{"$$mdtype":"Tag","name":"strong","attributes":{},"children":["Terminal."]}," A completed payment was returned in full."]}]}]}]}]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["A payment never re-enters an earlier state. ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["failureReason"]}," is populated for ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["rejected"]},", ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["failed"]},", and ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["canceled"]},". Treat the enum as ",{"$$mdtype":"Tag","name":"strong","attributes":{},"children":["open"]}," — tolerate statuses you don't recognize."]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":2,"id":"reading-a-payment"},"children":["Reading a payment"]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":[{"$$mdtype":"Tag","name":"code","attributes":{},"children":["GET /v1/payments/{paymentId}"]}," — scope ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["payments:read"]},". This is the authoritative view: reconcile against it whenever you miss a delivery or receive events out of order."]},{"$$mdtype":"Tag","name":"CodeBlock","attributes":{"data-language":"bash","header":{"controls":{"copy":{}}},"source":"curl 'https://public-sandbox.tryjeeves.com/v1/payments/7a3e5f21-9d64-4c18-b0a7-1f52e8c3d9b6' \\\n  -H \"Authorization: Bearer $JEEVES_ACCESS_TOKEN\"\n","lang":"bash"},"children":[]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["A payment belonging to another customer returns ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["404 payment_not_found"]},", not ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["403"]}," — the API never confirms the existence of a resource you can't access. ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["originalQuoteId"]}," is non-null only when the payment was requoted; in that case ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["quoteId"]}," names the quote that actually priced it."]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":2,"id":"the-webhook"},"children":["The webhook"]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["Jeeves sends ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["payment.status.updated"]}," to your registered callback URL on ",{"$$mdtype":"Tag","name":"strong","attributes":{},"children":["every"]}," status change. The callback URL, signing secret, optional mTLS, and IP allowlist are configured in the Jeeves web app — there is no registration API."]},{"$$mdtype":"Tag","name":"CodeBlock","attributes":{"data-language":"json","header":{"controls":{"copy":{}}},"source":"{\n  \"eventId\": \"evt_c91d20\",\n  \"eventType\": \"payment.status.updated\",\n  \"occurredAt\": \"2026-09-10T15:08:30Z\",\n  \"data\": {\n    \"paymentId\": \"7a3e5f21-9d64-4c18-b0a7-1f52e8c3d9b6\",\n    \"status\": \"in-progress\",\n    \"failureReason\": null\n  }\n}\n","lang":"json"},"children":[]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["Payloads carry the payment id, status, and failure reason only — never beneficiary or banking details."]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":2,"id":"verify-before-you-trust"},"children":["Verify before you trust"]},{"$$mdtype":"Tag","name":"ol","attributes":{},"children":[{"$$mdtype":"Tag","name":"li","attributes":{},"children":["Read ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["X-Jeeves-Timestamp"]}," and reject the delivery if the skew exceeds ",{"$$mdtype":"Tag","name":"strong","attributes":{},"children":["5 minutes"]},"."]},{"$$mdtype":"Tag","name":"li","attributes":{},"children":["Compute ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["HMAC-SHA256(secret, timestamp + \".\" + rawBody)"]},", hex-encoded, and compare to ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["X-Jeeves-Signature"]}," with a ",{"$$mdtype":"Tag","name":"strong","attributes":{},"children":["constant-time"]}," comparison."]},{"$$mdtype":"Tag","name":"li","attributes":{},"children":["Compute the HMAC over the ",{"$$mdtype":"Tag","name":"strong","attributes":{},"children":["raw request body"]},", before any JSON parsing or re-serialization — re-encoding changes the bytes and breaks the signature."]}]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":2,"id":"delivery-semantics"},"children":["Delivery semantics"]},{"$$mdtype":"Tag","name":"div","attributes":{"className":"md-table-wrapper"},"children":[{"$$mdtype":"Tag","name":"table","attributes":{"className":"md"},"children":[{"$$mdtype":"Tag","name":"thead","attributes":{},"children":[{"$$mdtype":"Tag","name":"tr","attributes":{},"children":[{"$$mdtype":"Tag","name":"th","attributes":{"align":"left","data-label":"Property"},"children":["Property"]},{"$$mdtype":"Tag","name":"th","attributes":{"align":"left","data-label":"What it means for you"},"children":["What it means for you"]}]}]},{"$$mdtype":"Tag","name":"tbody","attributes":{},"children":[{"$$mdtype":"Tag","name":"tr","attributes":{},"children":[{"$$mdtype":"Tag","name":"td","attributes":{"align":"left"},"children":["At-least-once"]},{"$$mdtype":"Tag","name":"td","attributes":{"align":"left"},"children":["The same event may arrive more than once — deduplicate on ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["eventId"]}," (also the ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["X-Jeeves-Event-Id"]}," header)."]}]},{"$$mdtype":"Tag","name":"tr","attributes":{},"children":[{"$$mdtype":"Tag","name":"td","attributes":{"align":"left"},"children":["Unordered"]},{"$$mdtype":"Tag","name":"td","attributes":{"align":"left"},"children":["Reconcile on ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["occurredAt"]}," and treat ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["GET /v1/payments/{id}"]}," as the source of truth. Never assume the latest delivery reflects the latest state."]}]},{"$$mdtype":"Tag","name":"tr","attributes":{},"children":[{"$$mdtype":"Tag","name":"td","attributes":{"align":"left"},"children":["10-second window"]},{"$$mdtype":"Tag","name":"td","attributes":{"align":"left"},"children":["Respond ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["2xx"]}," within 10 seconds, then process asynchronously. Anything else (or a timeout) is retried with exponential backoff for about ",{"$$mdtype":"Tag","name":"strong","attributes":{},"children":["26 hours"]},"."]}]},{"$$mdtype":"Tag","name":"tr","attributes":{},"children":[{"$$mdtype":"Tag","name":"td","attributes":{"align":"left"},"children":["Additive changes"]},{"$$mdtype":"Tag","name":"td","attributes":{"align":"left"},"children":["Tolerate unrecognized ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["status"]}," values and unrecognized fields."]}]},{"$$mdtype":"Tag","name":"tr","attributes":{},"children":[{"$$mdtype":"Tag","name":"td","attributes":{"align":"left"},"children":["Not rate-limited"]},{"$$mdtype":"Tag","name":"td","attributes":{"align":"left"},"children":["Webhook deliveries don't count against your API rate limits."]}]}]}]}]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":2,"id":"attaching-a-receipt"},"children":["Attaching a receipt"]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["Once a payment exists you can attach a receipt to it for reconciliation, audit and dispute. It is stored against the payment and appears alongside it in the Jeeves app, exactly as a receipt uploaded from the app does."]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":[{"$$mdtype":"Tag","name":"code","attributes":{},"children":["POST /v1/payments/{paymentId}/receipts"]}," — scope ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["payments:write"]},". The only ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["multipart/form-data"]}," endpoint in this API: send a single ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["file"]}," part."]},{"$$mdtype":"Tag","name":"CodeBlock","attributes":{"data-language":"bash","header":{"controls":{"copy":{}}},"source":"curl -X POST 'https://public-sandbox.tryjeeves.com/v1/payments/7a3e5f21-9d64-4c18-b0a7-1f52e8c3d9b6/receipts' \\\n  -H \"Authorization: Bearer $JEEVES_ACCESS_TOKEN\" \\\n  -F 'file=@payment-receipt-8821.pdf'\n","lang":"bash"},"children":[]},{"$$mdtype":"Tag","name":"CodeBlock","attributes":{"data-language":"json","header":{"controls":{"copy":{}}},"source":"{\n  \"receiptId\": \"0f9e8a05-12b1-438e-a95f-edf39969e884\",\n  \"paymentId\": \"7a3e5f21-9d64-4c18-b0a7-1f52e8c3d9b6\",\n  \"fileName\": \"payment-receipt-8821.pdf\",\n  \"contentType\": \"application/pdf\",\n  \"sizeBytes\": 184320,\n  \"createdAt\": \"2026-10-07T09:14:33Z\"\n}\n","lang":"json"},"children":[]},{"$$mdtype":"Tag","name":"ul","attributes":{},"children":[{"$$mdtype":"Tag","name":"li","attributes":{},"children":[{"$$mdtype":"Tag","name":"strong","attributes":{},"children":["One receipt per request, and it appends."]}," Send a single ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["file"]}," part; posting again adds another receipt rather than replacing the first."]},{"$$mdtype":"Tag","name":"li","attributes":{},"children":[{"$$mdtype":"Tag","name":"strong","attributes":{},"children":["The file is checked by its content, not its name."]}," Renaming a ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":[".exe"]}," to ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":[".pdf"]}," does not get it through. Supported: ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["pdf"]},", ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["jpeg"]},", ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["jpg"]},", ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["png"]},", ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["tiff"]},", up to ",{"$$mdtype":"Tag","name":"strong","attributes":{},"children":["5 MB"]},"."]},{"$$mdtype":"Tag","name":"li","attributes":{},"children":[{"$$mdtype":"Tag","name":"strong","attributes":{},"children":["Receipts are stored as documents only"]}," — they are never read, parsed or put through OCR."]},{"$$mdtype":"Tag","name":"li","attributes":{},"children":[{"$$mdtype":"Tag","name":"strong","attributes":{},"children":["No ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["Idempotency-Key"]}]},", and one that is sent is ignored. Nothing moves money here, so a duplicate upload costs you a duplicate receipt, not a duplicate payment."]}]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["Errors follow the usual envelope: ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["404 payment_not_found"]}," for an unknown payment or one belonging to another customer, ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["415 unsupported_media_type"]}," for an unsupported file type, ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["413 payload_too_large"]}," above 5 MB, ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["422 validation_failed"]}," when the payment's status does not accept receipts, and ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["400 validation_failed"]}," when no ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["file"]}," part is sent."]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":2,"id":"statements--exports"},"children":["Statements & exports"]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["For statement-style exports of completed payment activity, the Transactions API's ",{"$$mdtype":"Tag","name":"a","attributes":{"href":"/apis#tag/Transactions/operation/PaymentsController.getTransactions"},"children":[{"$$mdtype":"Tag","name":"code","attributes":{},"children":["GET /v1/payments/transactions"]}]}," lists completed payment transactions by date range with cursor pagination."]}]},"headings":[{"value":"Tracking & webhooks","id":"tracking--webhooks","depth":1},{"value":"The lifecycle","id":"the-lifecycle","depth":2},{"value":"Reading a payment","id":"reading-a-payment","depth":2},{"value":"The webhook","id":"the-webhook","depth":2},{"value":"Verify before you trust","id":"verify-before-you-trust","depth":2},{"value":"Delivery semantics","id":"delivery-semantics","depth":2},{"value":"Attaching a receipt","id":"attaching-a-receipt","depth":2},{"value":"Statements & exports","id":"statements--exports","depth":2}],"frontmatter":{"seo":{"title":"Tracking & webhooks"}},"lastModified":"2026-11-04T03:42:09.000Z","pagePropGetterError":{"message":"","name":""}},"slug":"/payments/payment-flow/tracking-payments","userData":{"isAuthenticated":false,"teams":["anonymous"]}}