{"templateId":"markdown","sharedDataIds":{"sidebar":"sidebar-sidebars.yaml"},"props":{"metadata":{"markdoc":{"tagList":[]},"type":"markdown"},"seo":{"title":"Webhook Setup","description":"Complete documentation for integrating Cheqi's digital receipt platform","keywords":["cheqi","digital receipts","api","sdk","java","javascript"],"llmstxt":{"hide":false,"sections":[{"title":"Table of contents","includeFiles":["**/*"],"excludeFiles":[]}],"excludeFiles":[]}},"dynamicMarkdocComponents":[],"compilationErrors":[],"ast":{"$$mdtype":"Tag","name":"article","attributes":{},"children":[{"$$mdtype":"Tag","name":"Heading","attributes":{"level":1,"id":"webhook-setup","__idx":0},"children":["Webhook Setup"]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["Register an HTTPS endpoint to receive Cheqi receipt, credit-note, and return-request events."]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":2,"id":"prerequisites","__idx":1},"children":["Prerequisites"]},{"$$mdtype":"Tag","name":"ul","attributes":{},"children":[{"$$mdtype":"Tag","name":"li","attributes":{},"children":["A company or client-application access token with ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["read_receipts"]},"."]},{"$$mdtype":"Tag","name":"li","attributes":{},"children":["A publicly reachable HTTPS endpoint."]},{"$$mdtype":"Tag","name":"li","attributes":{},"children":["A webhook secret configured for the subscription owner."]},{"$$mdtype":"Tag","name":"li","attributes":{},"children":["Durable asynchronous processing and idempotency."]}]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":2,"id":"register-a-subscription","__idx":2},"children":["Register a subscription"]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["Create subscriptions with ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["POST /webhook/subscription"]},":"]},{"$$mdtype":"Tag","name":"CodeBlock","attributes":{"data-language":"bash","header":{"controls":{"copy":{}}},"source":"curl --request POST 'https://api.cheqi.io/webhook/subscription' \\\n  --header 'Authorization: Bearer YOUR_ACCESS_TOKEN' \\\n  --header 'Content-Type: application/json' \\\n  --data '{\n    \"name\": \"Production webhook\",\n    \"notificationUrl\": \"https://your-domain.example/webhooks/cheqi\",\n    \"events\": [\n      \"RECEIPT_CREATED\",\n      \"RETURN_REQUESTED\",\n      \"CREDIT_NOTE_CREATED\"\n    ]\n  }'\n","lang":"bash"},"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":{"data-label":"Property"},"children":["Property"]},{"$$mdtype":"Tag","name":"th","attributes":{"data-label":"Required"},"children":["Required"]},{"$$mdtype":"Tag","name":"th","attributes":{"data-label":"Description"},"children":["Description"]}]}]},{"$$mdtype":"Tag","name":"tbody","attributes":{},"children":[{"$$mdtype":"Tag","name":"tr","attributes":{},"children":[{"$$mdtype":"Tag","name":"td","attributes":{},"children":[{"$$mdtype":"Tag","name":"code","attributes":{},"children":["name"]}]},{"$$mdtype":"Tag","name":"td","attributes":{},"children":["Yes"]},{"$$mdtype":"Tag","name":"td","attributes":{},"children":["Human-readable subscription name."]}]},{"$$mdtype":"Tag","name":"tr","attributes":{},"children":[{"$$mdtype":"Tag","name":"td","attributes":{},"children":[{"$$mdtype":"Tag","name":"code","attributes":{},"children":["notificationUrl"]}]},{"$$mdtype":"Tag","name":"td","attributes":{},"children":["Yes"]},{"$$mdtype":"Tag","name":"td","attributes":{},"children":["Public HTTPS endpoint that accepts Cheqi webhook requests."]}]},{"$$mdtype":"Tag","name":"tr","attributes":{},"children":[{"$$mdtype":"Tag","name":"td","attributes":{},"children":[{"$$mdtype":"Tag","name":"code","attributes":{},"children":["events"]}]},{"$$mdtype":"Tag","name":"td","attributes":{},"children":["Yes"]},{"$$mdtype":"Tag","name":"td","attributes":{},"children":["Event types to subscribe to."]}]}]}]}]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["Cheqi creates one subscription per requested event type. Existing active subscriptions for the same owner and event are not duplicated."]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":2,"id":"request-format","__idx":3},"children":["Request format"]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["Cheqi sends JSON with two top-level properties:"]},{"$$mdtype":"Tag","name":"CodeBlock","attributes":{"data-language":"json","header":{"controls":{"copy":{}}},"source":"{\n  \"event\": \"RECEIPT_CREATED\",\n  \"data\": {\n    \"encryptedReceipt\": {\n      \"cheqiReceiptId\": \"CHQ-20260803-ABC123\",\n      \"encryptedEnvelope\": \"base64-ciphertext...\",\n      \"encryptedEnvelopeKey\": \"base64-wrapped-key...\"\n    }\n  }\n}\n","lang":"json"},"children":[]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["The object inside ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["data"]}," depends on ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["event"]},". See ",{"$$mdtype":"Tag","name":"MarkdownLink","attributes":{"href":"/webhooks/events"},"children":["Webhook Events"]}," for the complete shapes."]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["Requests include these headers:"]},{"$$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":{"data-label":"Header"},"children":["Header"]},{"$$mdtype":"Tag","name":"th","attributes":{"data-label":"Description"},"children":["Description"]}]}]},{"$$mdtype":"Tag","name":"tbody","attributes":{},"children":[{"$$mdtype":"Tag","name":"tr","attributes":{},"children":[{"$$mdtype":"Tag","name":"td","attributes":{},"children":[{"$$mdtype":"Tag","name":"code","attributes":{},"children":["Content-Type"]}]},{"$$mdtype":"Tag","name":"td","attributes":{},"children":[{"$$mdtype":"Tag","name":"code","attributes":{},"children":["application/json"]},"."]}]},{"$$mdtype":"Tag","name":"tr","attributes":{},"children":[{"$$mdtype":"Tag","name":"td","attributes":{},"children":[{"$$mdtype":"Tag","name":"code","attributes":{},"children":["User-Agent"]}]},{"$$mdtype":"Tag","name":"td","attributes":{},"children":["Cheqi webhook service identifier."]}]},{"$$mdtype":"Tag","name":"tr","attributes":{},"children":[{"$$mdtype":"Tag","name":"td","attributes":{},"children":[{"$$mdtype":"Tag","name":"code","attributes":{},"children":["X-Cheqi-Event-Type"]}]},{"$$mdtype":"Tag","name":"td","attributes":{},"children":["Event type, also present in the JSON body."]}]},{"$$mdtype":"Tag","name":"tr","attributes":{},"children":[{"$$mdtype":"Tag","name":"td","attributes":{},"children":[{"$$mdtype":"Tag","name":"code","attributes":{},"children":["X-Cheqi-Signature"]}]},{"$$mdtype":"Tag","name":"td","attributes":{},"children":[{"$$mdtype":"Tag","name":"code","attributes":{},"children":["sha256="]}," followed by the Base64 HMAC-SHA256 signature when a secret is configured."]}]}]}]}]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":2,"id":"implement-the-endpoint","__idx":4},"children":["Implement the endpoint"]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["Signature verification must use the exact raw HTTP bytes. Do not bind the body to an object before verifying the signature, because parsing and re-serialization can change the byte sequence."]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":3,"id":"java-example","__idx":5},"children":["Java example"]},{"$$mdtype":"Tag","name":"CodeBlock","attributes":{"data-language":"java","header":{"controls":{"copy":{}}},"source":"@RestController\n@RequestMapping(\"/webhooks\")\npublic class CheqiWebhookController {\n    private final WebhookSignatureVerifier signatureVerifier;\n    private final ObjectMapper objectMapper;\n    private final WebhookQueue webhookQueue;\n\n    @PostMapping(\"/cheqi\")\n    public ResponseEntity<Void> receive(\n            @RequestBody byte[] rawBody,\n            @RequestHeader(\"X-Cheqi-Signature\") String signature\n    ) throws IOException {\n        if (!signatureVerifier.isValid(rawBody, signature)) {\n            return ResponseEntity.status(401).build();\n        }\n\n        WebhookEvent event = objectMapper.readValue(rawBody, WebhookEvent.class);\n        webhookQueue.enqueue(event, rawBody);\n        return ResponseEntity.ok().build();\n    }\n}\n","lang":"java"},"children":[]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":3,"id":"nodejs-example","__idx":6},"children":["Node.js example"]},{"$$mdtype":"Tag","name":"CodeBlock","attributes":{"data-language":"javascript","header":{"controls":{"copy":{}}},"source":"import express from \"express\";\n\nconst app = express();\n\napp.post(\n  \"/webhooks/cheqi\",\n  express.raw({ type: \"application/json\" }),\n  async (req, res) => {\n    const signature = req.header(\"X-Cheqi-Signature\");\n\n    if (!verifyCheqiSignature(req.body, signature)) {\n      return res.sendStatus(401);\n    }\n\n    const event = JSON.parse(req.body.toString(\"utf8\"));\n    await webhookQueue.enqueue(event, req.body);\n    return res.sendStatus(200);\n  }\n);\n","lang":"javascript"},"children":[]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":2,"id":"route-events","__idx":7},"children":["Route events"]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["Route on ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["event"]},", then select the matching object in ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["data"]},":"]},{"$$mdtype":"Tag","name":"CodeBlock","attributes":{"data-language":"java","header":{"controls":{"copy":{}}},"source":"switch (event.event()) {\n    case RECEIPT_CREATED -> processReceipt(event.data().encryptedReceipt());\n    case RETURN_REQUESTED -> processReturn(event.data().creditNoteInitiationRequest());\n    case CREDIT_NOTE_CREATED -> processCreditNote(event.data().encryptedCreditNote());\n}\n","lang":"java"},"children":[]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["For encrypted receipt and credit-note events, decrypt ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["encryptedEnvelope"]}," into a ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["ReceiptEnvelope"]}," and read its ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["documents"]}," map. See ",{"$$mdtype":"Tag","name":"MarkdownLink","attributes":{"href":"/webhooks/receipt-webhooks"},"children":["Receipt Webhooks"]}," and ",{"$$mdtype":"Tag","name":"MarkdownLink","attributes":{"href":"/webhooks/return-webhooks"},"children":["Return Webhooks"]},"."]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":2,"id":"idempotency","__idx":8},"children":["Idempotency"]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["Webhook delivery is at least once. A retry contains the same serialized payload recorded for the initial attempt. Establish idempotency before performing downstream writes."]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["A practical key is:"]},{"$$mdtype":"Tag","name":"CodeBlock","attributes":{"data-language":"text","header":{"controls":{"copy":{}}},"source":"event + cheqiReceiptId + authorization context\n","lang":"text"},"children":[]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["The authorization context can be the applicable ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["clientId"]},", ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["companyId"]},", or ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["userId"]}," from the event-specific object."]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["Return ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["2xx"]}," after the event is durably accepted, not after all decryption and downstream synchronization has finished."]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":2,"id":"local-testing","__idx":9},"children":["Local testing"]},{"$$mdtype":"Tag","name":"ol","attributes":{},"children":[{"$$mdtype":"Tag","name":"li","attributes":{},"children":["Start your webhook endpoint locally."]},{"$$mdtype":"Tag","name":"li","attributes":{},"children":["Expose it through an HTTPS tunnel."]},{"$$mdtype":"Tag","name":"li","attributes":{},"children":["Register the tunnel URL as ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["notificationUrl"]}," in a non-production Cheqi environment."]},{"$$mdtype":"Tag","name":"li","attributes":{},"children":["Trigger a real event in that environment."]},{"$$mdtype":"Tag","name":"li","attributes":{},"children":["Capture the exact raw body and signature header for repeatable signature tests."]}]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["Do not use fabricated plaintext receipt contents to test production flows. Test encryption and decryption with the same SDK envelope implementation used by your integration."]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":2,"id":"troubleshooting","__idx":10},"children":["Troubleshooting"]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":3,"id":"signature-verification-fails","__idx":11},"children":["Signature verification fails"]},{"$$mdtype":"Tag","name":"ul","attributes":{},"children":[{"$$mdtype":"Tag","name":"li","attributes":{},"children":["Verify before JSON parsing."]},{"$$mdtype":"Tag","name":"li","attributes":{},"children":["Hash the exact raw bytes, including whitespace."]},{"$$mdtype":"Tag","name":"li","attributes":{},"children":["Remove the ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["sha256="]}," prefix only when comparing the decoded digest rather than the complete header value."]},{"$$mdtype":"Tag","name":"li","attributes":{},"children":["Confirm the secret belongs to the subscription owner that received the event."]}]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":3,"id":"events-are-retried","__idx":12},"children":["Events are retried"]},{"$$mdtype":"Tag","name":"ul","attributes":{},"children":[{"$$mdtype":"Tag","name":"li","attributes":{},"children":["Return a ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["2xx"]}," response promptly."]},{"$$mdtype":"Tag","name":"li","attributes":{},"children":["Persist first and process asynchronously."]},{"$$mdtype":"Tag","name":"li","attributes":{},"children":["Inspect your endpoint latency and non-2xx responses."]}]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":3,"id":"envelope-decryption-fails","__idx":13},"children":["Envelope decryption fails"]},{"$$mdtype":"Tag","name":"ul","attributes":{},"children":[{"$$mdtype":"Tag","name":"li","attributes":{},"children":["Select the private key matching the webhook's public-key snapshot."]},{"$$mdtype":"Tag","name":"li","attributes":{},"children":["Respect ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["recipientKeyAlgorithm"]},"."]},{"$$mdtype":"Tag","name":"li","attributes":{},"children":["Do not Base64-decode or transform the ciphertext more than required by the SDK."]},{"$$mdtype":"Tag","name":"li","attributes":{},"children":["Keep historical private keys available after key rotation."]}]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["Continue with ",{"$$mdtype":"Tag","name":"MarkdownLink","attributes":{"href":"/webhooks/security"},"children":["Webhook Security"]},"."]}]},"headings":[{"value":"Webhook Setup","id":"webhook-setup","depth":1},{"value":"Prerequisites","id":"prerequisites","depth":2},{"value":"Register a subscription","id":"register-a-subscription","depth":2},{"value":"Request format","id":"request-format","depth":2},{"value":"Implement the endpoint","id":"implement-the-endpoint","depth":2},{"value":"Java example","id":"java-example","depth":3},{"value":"Node.js example","id":"nodejs-example","depth":3},{"value":"Route events","id":"route-events","depth":2},{"value":"Idempotency","id":"idempotency","depth":2},{"value":"Local testing","id":"local-testing","depth":2},{"value":"Troubleshooting","id":"troubleshooting","depth":2},{"value":"Signature verification fails","id":"signature-verification-fails","depth":3},{"value":"Events are retried","id":"events-are-retried","depth":3},{"value":"Envelope decryption fails","id":"envelope-decryption-fails","depth":3}],"frontmatter":{"seo":{"title":"Webhook Setup"}},"lastModified":"2026-08-03T14:23:19.000Z","pagePropGetterError":{"message":"","name":""}},"slug":"/webhooks/setup","userData":{"isAuthenticated":false,"teams":["anonymous"]},"isPublic":true}