Skip to content

Every Cheqi webhook has an event and a data property. The key inside data identifies the object delivered by that event.

Common structure

{
  "event": "EVENT_NAME",
  "data": {
    "eventSpecificObject": {}
  }
}

The event-specific object may contain this routing metadata:

FieldDescription
clientIdPublic OAuth client ID for the webhook subscription, when owned by a client application.
companyIdCustomer-company context, when applicable.
userIdCustomer-user context, when applicable.
created_atTime Cheqi created the webhook event.
cheqiReceiptIdCheqi identifier of the receipt or credit note.

Null and empty fields are omitted. Do not assume both companyId and userId are present.

RECEIPT_CREATED

data.encryptedReceipt contains the encrypted document envelope produced by the elected owner device for your integration.

{
  "event": "RECEIPT_CREATED",
  "data": {
    "encryptedReceipt": {
      "clientId": "your-public-client-id",
      "companyId": "550e8400-e29b-41d4-a716-446655440000",
      "created_at": "2026-08-03T13:30:00Z",
      "cheqiReceiptId": "CHQ-20260803-ABC123",
      "encryptedEnvelope": "base64-ciphertext...",
      "encryptedEnvelopeKey": "base64-wrapped-aes-key...",
      "publicKey": "base64-recipient-public-key-snapshot...",
      "recipientKeyAlgorithm": "RSA_2048",
      "envelopeVersion": 1,
      "receiptGeneratorVersion": "0.3.0",
      "finalHash": "sha256-cheqi-document-hash..."
    }
  }
}

Use publicKey and recipientKeyAlgorithm to select the corresponding private key and unwrap encryptedEnvelopeKey. Decrypt encryptedEnvelope, then deserialize its UTF-8 plaintext as a ReceiptEnvelope.

finalHash is the definitive CHEQI document hash submitted by the elected device together with the encrypted deliveries. There is no separate receipt-finalization webhook.

RETURN_REQUESTED

The customer's device encrypts a CreditNoteInitiationRequest for the original issuer. Cheqi routes that ciphertext without decrypting it.

{
  "event": "RETURN_REQUESTED",
  "data": {
    "creditNoteInitiationRequest": {
      "clientId": "issuer-public-client-id",
      "companyId": "550e8400-e29b-41d4-a716-446655440000",
      "userId": "8ccf09a7-d969-4a09-880f-42d520a0999a",
      "created_at": "2026-08-03T14:00:00Z",
      "cheqiReceiptId": "CHQ-20260803-ABC123",
      "publicKey": "base64-issuer-public-key...",
      "encryptedCreditNoteInitiationRequest": "base64-ciphertext...",
      "encryptedSymmetricKey": "base64-wrapped-aes-key..."
    }
  }
}

After decryption, the plaintext is a CreditNoteInitiationRequest containing the original receipt identifiers, requested return line items, the customer's note, and refund preference. It is not a ReceiptEnvelope.

CREDIT_NOTE_CREATED

Credit notes use the same encrypted document-bundle contract as receipts. The event-specific key is encryptedCreditNote.

{
  "event": "CREDIT_NOTE_CREATED",
  "data": {
    "encryptedCreditNote": {
      "clientId": "your-public-client-id",
      "companyId": "550e8400-e29b-41d4-a716-446655440000",
      "created_at": "2026-08-03T15:00:00Z",
      "cheqiReceiptId": "CHQ-CN-20260803-DEF456",
      "encryptedEnvelope": "base64-ciphertext...",
      "encryptedEnvelopeKey": "base64-wrapped-aes-key...",
      "publicKey": "base64-recipient-public-key-snapshot...",
      "recipientKeyAlgorithm": "RSA_2048",
      "envelopeVersion": 1,
      "receiptGeneratorVersion": "0.3.0",
      "finalHash": "sha256-cheqi-credit-note-hash..."
    }
  }
}

Decrypt encryptedEnvelope as a ReceiptEnvelope. Its documents map contains CHEQI and, when requested by the recipient, UBL_CREDIT_NOTE.

finalHash is the definitive CHEQI credit-note hash submitted by the elected device together with the encrypted deliveries. There is no separate credit-note finalization webhook.

ReceiptEnvelope

The plaintext obtained by decrypting a receipt or credit-note encryptedEnvelope has this versioned structure:

{
  "envelopeVersion": 1,
  "receiptUuid": "6f9619ff-8b86-d011-b42d-00cf4fc964ff",
  "cheqiReceiptId": "CHQ-20260803-ABC123",
  "receiptGeneratorVersion": "0.3.0",
  "documents": {
    "CHEQI": {
      "mediaType": "application/json",
      "content": "{...}"
    },
    "UBL_INVOICE": {
      "mediaType": "application/xml",
      "content": "<Invoice>...</Invoice>"
    }
  }
}
PropertyDescription
envelopeVersionVersion of the encrypted plaintext-envelope contract.
receiptUuidShared UUID for the locally generated document bundle.
cheqiReceiptIdCheqi receipt or credit-note identifier.
receiptGeneratorVersionRust receipt-engine version used to generate the documents.
documentsMap keyed by CHEQI, UBL_INVOICE, UBL_PURCHASE_RECEIPT, or UBL_CREDIT_NOTE.

Each document has a mediaType and string content. The keys actually present in documents are authoritative; there is no separate receiptFormats property in the webhook.

The envelope already contains the complete generated documents, including their supplier, customer, and payment context. There is no separate encryptedCustomerDetails or encryptedCustomerAesKey payload.

Processing steps

  1. Verify the webhook signature against the raw HTTP body.
  2. Persist the event or establish idempotency before returning success.
  3. Select the private key corresponding to the supplied public-key snapshot.
  4. Unwrap the AES key from encryptedEnvelopeKey according to recipientKeyAlgorithm.
  5. Decrypt encryptedEnvelope using the envelope encryption implementation in the Cheqi SDK.
  6. Deserialize the plaintext as ReceiptEnvelope.
  7. Read the required representation from ReceiptEnvelope.documents.
  8. Canonicalize and hash the CHEQI document when independently verifying finalHash.
Do not log plaintext

Receipt and return contents can contain personal and financial data. Do not log ciphertext keys, decrypted envelopes, or document contents.

Idempotency and retries

Cheqi can retry a webhook when delivery fails. Process events idempotently. A practical idempotency key is event + cheqiReceiptId; if your application can receive the same receipt for multiple authorization contexts, include the applicable clientId, companyId, or userId as well.

Always return 2xx only after the raw event has been durably accepted. Perform decryption and downstream synchronization asynchronously.