> ## Documentation Index
> Fetch the complete documentation index at: https://docs.chargeblast.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Digital receipt lookup pull guide

> Use the pull path when you want Chargeblast to ask your own systems for receipt details in real time—ideal if you already resolve orders behind an API such as GET /orders/:id instead of sending everything to us up front.

This guide covers **digital receipt lookup** on the **pull** path: when someone looks up a charge, Chargeblast **calls your HTTPS endpoint** and your application returns the receipt information, rather than Chargeblast reading it from data you have already stored with us (similar to how you might serve an order from **GET `/orders/:id`** in your own API).

You will expose an endpoint that receives each lookup request, optionally checks that the call really came from Chargeblast, finds the matching transaction in your systems, and answers with **found** (success) or **not found**—the following sections spell out headers, security, and the exact JSON shape.

<Info>
  This is the **pull** path described under [Digital Receipts & Deflections](/guides/deflections-implementation) (Pull Data). Return to that page for the high-level implementation steps and qualifying fields for deflection.
</Info>

***

## 1. Enabling the feature

In Chargeblast (Developer settings):

1. Set **Digital receipt lookup URL** to your host and path **without** the `https://` prefix (for example `api.example.com/v1/chargeblast/digital_receipt_lookup`). Chargeblast calls **`https://{that value}`**.
2. Turn the toggle **on**.
3. Note two secrets (both can be rotated independently):
   * **Lookup key** — sent as plaintext in `X-Digital-Receipt-Lookup-Key`.
   * **Lookup signature key** — used only on your side to verify **`X-Digital-Receipt-Signature`** (HMAC; see [HMAC-SHA256 signature](#4-hmac-sha256-signature-algorithm)).

### Testing in the app

The Chargeblast app includes **developer tooling** so you can validate your integration without waiting for live issuer traffic. You can **send test lookup events** to your URL, **replay** earlier requests to iterate on matching and receipt JSON, and review timing, headers, and validation feedback in one place—so you can confirm HMAC handling, response shape, and SLA before production lookups hit your endpoint.

Use **Send test webhook** (and related controls in the same area) to drive those runs and inspect the exact **cURL**, payloads, and hints the UI surfaces.

<img src="https://mintcdn.com/chargeblast/ntrX-tzDCxYfNFkO/images/reference/digital-receipt-lookup-test-tool.png?fit=max&auto=format&n=ntrX-tzDCxYfNFkO&q=85&s=1cc417054c4622f972e162a08dc52acd" alt="" width="2954" height="2188" data-path="images/reference/digital-receipt-lookup-test-tool.png" />

***

## 2. Request method, URL, and headers

| Item                           | Value                                                                                                                                                                      |
| ------------------------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Method                         | `POST`                                                                                                                                                                     |
| URL                            | `https://{your configured host path}`                                                                                                                                      |
| `Content-Type`                 | `application/json`                                                                                                                                                         |
| `X-Event-Type`                 | `digital_receipt.lookup`                                                                                                                                                   |
| `X-Digital-Receipt-Lookup-Key` | Your **lookup key** (plaintext shared secret).                                                                                                                             |
| `X-Digital-Receipt-Signature`  | **HMAC-SHA256** of the **exact raw request body**, encoded as **lowercase hexadecimal** (no `0x` prefix). See [HMAC-SHA256 signature](#4-hmac-sha256-signature-algorithm). |

### Where the digest is (no separate timestamp)

* **The digest is the header value** `X-Digital-Receipt-Signature`. It is **not** in the JSON body. The string is exactly **64 characters** (`0`–`9`, `a`–`f`): the hex encoding of the 32-byte HMAC-SHA256 output. There is no separate `Digest` field, body field, or second signature—if you do not see a digest, check **request headers**, not the body. (The dashboard **Send test webhook** cURL includes the same header.)
* **There is no timestamp header** (for example no `X-Digital-Receipt-Timestamp`) and **nothing is prepended or appended** to the body before HMAC. The MAC uses **only** the **lookup signature key** (UTF-8, as the HMAC key) and the **raw POST body bytes** (as the message). **You do not need a timestamp** to compute or verify the signature: re-read the **exact** body bytes from the request, run the same HMAC with your stored secret, and compare to the header (prefer **constant-time** comparison).
* **Replay:** The signature **does not** by itself prevent replay of a captured valid request. Mitigate with **HTTPS**, **rotating keys**, **idempotent** handlers, and your own rate limiting as needed.

Chargeblast **always sends both** the lookup key header and the signature header. **Your server** decides whether to enforce one or both (see [Using only the lookup key](#5-using-only-the-lookup-key-weaker-authentication)).

***

## 3. Request body (wire format)

The JSON body is derived from Chargeblast’s **internal search object** for digital receipt lookups. It is a **single normalized payload** for **Visa, Mastercard, Discover, and Amex** search requests—implement **one** matching path regardless of network.

### 3.1 Primary keys for matching (always present)

These fields are **guaranteed** on every lookup. Use them as the primary keys when resolving the transaction in your system:

| Field             | Notes                                         |
| ----------------- | --------------------------------------------- |
| `cardBin`         | First six digits of the PAN                   |
| `cardLast4`       | Last four digits of the PAN                   |
| `currency`        | Transaction currency (e.g. `"USD"`)           |
| `arn`             | Acquirer reference number                     |
| `authCode`        | Authorization code                            |
| `transactionDate` | Transaction timestamp (ISO-8601-style string) |
| `descriptor`      | Billing / payment descriptor for the charge   |

### 3.2 `source`

The **`source`** field indicates **where the lookup was initiated**—for example a **call center**, an **issuer banking or mobile app**, or another channel. Network and product determine the exact values; use it for logging, routing, or extra context alongside the guaranteed match keys above.

### 3.3 JSON serialization (critical for HMAC)

The body is built as **compact JSON** with:

* **`JSONSerialization` with `.sortedKeys`** — keys are in **sorted order** at every object level.
* UTF-8 encoding.

**You must verify the HMAC over the exact byte sequence Chargeblast sent** (the raw HTTP body). If you re-parse and re-serialize JSON (different key order, spacing, or number formatting), the signature will **not** match even with the correct secret.

### 3.4 Other fields (optional)

Additional keys may appear depending on the transaction (for example `amount`, `paymentType`, `token`, `terminalId`, `acquirerBin`, `cardAcceptorId`, `mcsn`, `purchaseIdentifier`, `transactionId`, `transactionType`, `cardExpirationDate`, `mcc`, `posEntryModeCode`, `eci`). Treat them as **supplemental** for matching or display—not substitutes for the guaranteed fields in [Primary keys for matching](#31-primary-keys-for-matching-always-present).

***

## 4. HMAC-SHA256 signature algorithm

This is **not** a bare SHA-256 hash of the body. It is **HMAC-SHA256**:

`signature = HMAC-SHA256(secret, bodyUTF8)`

as defined in RFC 2104, using SHA-256 as the hash function (FIPS-compatible HMAC-SHA256). There is **no timestamp** in the scheme: verification is **only** key + raw body → hex MAC → compare to `X-Digital-Receipt-Signature` (see [Where the digest is](#where-the-digest-is-no-separate-timestamp)).

### 4.1 Steps (normative for interoperability)

1. Let **`secret`** be the **lookup signature key** string from Chargeblast settings (UTF-8 bytes).
2. Let **`body`** be the **raw HTTP request body** as received (the same bytes as in `Content-Length`). Treat it as a UTF-8 string only if you know it is valid UTF-8 JSON; the HMAC is over **those bytes**.
3. Compute **`HMAC_SHA256(secret_utf8, body_bytes)`** using the standard library for your language (see examples below).
4. Encode the 32-byte MAC as **lowercase hex** (64 characters): each byte as two hex digits `00`–`ff`, no separators, no `0x`.
5. Compare that string to **`X-Digital-Receipt-Signature`** using **constant-time** equality when possible. Do **not** expect a timestamp header or a `t=` / `v1=` style signed prefix—those are **not** used here.

### 4.2 Reference implementation (Chargeblast server)

The server uses Swift **CryptoKit**-style HMAC:

* Key material: `SymmetricKey(data: Data(secret.utf8))`
* Message: `Data(body.utf8)` where `body` is the **exact JSON string** used as the POST body
* Output: hex string via `%02hhx` per byte (lowercase), set as the **`X-Digital-Receipt-Signature`** header value

### 4.3 Examples (Node.js, Python, Go)

Use the **raw** body buffer from your HTTP framework (**before** JSON parsing) as the HMAC message. Compare the computed hex to the header with constant-time equality when available.

<CodeGroup>
  ```javascript digital_receipt_signature.js theme={null}
  const crypto = require("crypto");

  function digitalReceiptSignature(secret, bodyBuffer) {
    return crypto.createHmac("sha256", secret).update(bodyBuffer).digest("hex");
  }
  ```

  ```python digital_receipt_signature.py theme={null}
  import hmac
  import hashlib

  def digital_receipt_signature(secret: str, body: bytes) -> str:
      return hmac.new(secret.encode("utf-8"), body, hashlib.sha256).hexdigest()
  ```

  ```go digital_receipt_signature.go theme={null}
  import (
      "crypto/hmac"
      "crypto/sha256"
      "encoding/hex"
  )

  func DigitalReceiptSignature(secret string, body []byte) string {
      mac := hmac.New(sha256.New, []byte(secret))
      mac.Write(body)
      return hex.EncodeToString(mac.Sum(nil))
  }
  ```
</CodeGroup>

In Python, compare with `hmac.compare_digest(computed, header_value)`.

### 4.4 Verification checklist (signature mismatches)

* Using the **signature** secret, not the lookup key.
* HMAC is over **raw body bytes**, not pretty-printed JSON.
* Same **sorted-key** canonical form as the sender (or read body as sent and do not reserialize before verifying).
* Compare hex **case-insensitively** if your stack emits uppercase (Chargeblast sends lowercase).
* Expecting a **timestamp** or signed-prefix scheme—**none** is sent; only the header + body HMAC applies.

***

## 5. Using only the lookup key (weaker authentication)

**Recommended:** verify **`X-Digital-Receipt-Signature`** with your **signature secret** **and** require **`X-Digital-Receipt-Lookup-Key`** to equal your **lookup key**. That way:

* Possession of the **lookup key** alone is not enough to forge payloads without knowing the **signature secret** (and without breaking HMAC).
* The body cannot be tampered with in transit without invalidating the MAC (assuming the secret stays private).

**Simpler / weaker option:** you may **only** check that **`X-Digital-Receipt-Lookup-Key`** matches your configured lookup key and **skip** HMAC verification.

| Approach              | Pros                                            | Cons                                                                                                                                                      |
| --------------------- | ----------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Lookup key **only**   | Easiest to implement; no crypto code.           | Anyone who learns the single secret can impersonate Chargeblast to your endpoint if they can reach it. No integrity check on the body (rely on TLS only). |
| Lookup key **+** HMAC | Stronger authentication and **body integrity**. | Must use raw body bytes and correct HMAC-SHA256 hex.                                                                                                      |

Chargeblast **still sends** both headers; skipping signature verification is **your** policy choice.

***

## 6. Response: HTTP status

| Status          | Meaning                                                                                                  | Body                                                                                                                                                                     |
| --------------- | -------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| **200**–**299** | Transaction **found** (success).                                                                         | JSON receipt in the **minimal merchant shape** (`order`, `merchantProfile`, `accountProfile`); see [Minimal successful JSON](#7-minimal-successful-json-top-level-keys). |
| **404**         | Transaction **not** found.                                                                               | Body may be empty; Chargeblast treats this as “not found” without parsing JSON.                                                                                          |
| Other           | Treated as errors by Chargeblast for production lookup flows; use **200** / **404** for normal outcomes. |                                                                                                                                                                          |

You do **not** need a top-level `response` wrapper. **`responseStatus` in JSON is optional** for minimal shape; found vs not found is primarily driven by **HTTP status** (200 vs 404).

***

## 7. Minimal successful JSON (top-level keys)

Chargeblast accepts a minimal object with top-level **`order`**, **`merchantProfile`**, and **`accountProfile`** (and optional **`responseStatus`**).

### 7.1 Required for a valid minimal receipt (decode succeeds)

These are enforced for the **minimal** path (structural validation). Amounts are **strings** in the merchant JSON (e.g. `"15.00"`), not necessarily the same type as the inbound `amount` number.

| Path                                                          | Requirement                                   |
| ------------------------------------------------------------- | --------------------------------------------- |
| `order.merchantOrderId`                                       | Non-empty after trim                          |
| `order.orderDateTime`                                         | Non-empty after trim                          |
| `order.total`                                                 | Non-empty after trim                          |
| `order.currencyCode`                                          | Non-empty after trim (ISO-like currency code) |
| `order.orderItems`                                            | At least **one** item                         |
| `order.orderItems[0].productName` **or** `productDescription` | At least one non-empty after trim             |
| `merchantProfile.name`                                        | Non-empty after trim                          |
| `merchantProfile.merchantReceiptContact.phoneForReceipt`      | Non-empty after trim                          |
| `merchantProfile.merchantReceiptContact.websiteForReceipt`    | Non-empty after trim                          |
| `accountProfile.email` **or** `accountProfile.phone`          | At least one non-empty after trim             |

### 7.2 Optional on the minimal path (but useful)

| Path                                                                                   | Notes                                         |
| -------------------------------------------------------------------------------------- | --------------------------------------------- |
| `order.subtotal`                                                                       | Defaults to `order.total` if omitted or blank |
| `order.orderPhone`                                                                     | Optional                                      |
| `order.transactionDetails.deviceIpAddress`                                             | **Not required** for a minimal valid receipt  |
| `merchantProfile.merchantReceiptContact.emailForReceipt`                               | Optional                                      |
| `merchantProfile.logoUrl`, `description`, policy links, `termsAndConditionsLink`, etc. | Optional                                      |
| `accountProfile.name` (`givenName` / `familyName`)                                     | Optional                                      |
| `accountProfile.accountBillingAddress` (`city`, `country`, `postalCode`)               | Optional                                      |

### 7.3 Compelling evidence (CE) vs “valid receipt”

A response can be **structurally valid** (decodes to a receipt) but still **fail compelling-evidence rules** used for disputes. In particular:

* **`order.transactionDetails.deviceIpAddress`** is **important for CE** but **not** part of the minimal required-field list above.
* Other CE-related gaps are derived from the mapped receipt shape (merchant descriptors, product description, customer identifiers, delivery address, etc.).

The Chargeblast **developer test** API merges structural and CE gaps into one list. CE-only gaps may appear in payloads with the suffix **` (required for compelling evidence)`**; in the **Send test webhook** UI, those paths are shown with a **\*** and a short legend instead of that full suffix.

***

## 8. Example minimal body (illustrative)

```json theme={null}
{
  "order": {
    "merchantOrderId": "ord_123",
    "orderDateTime": "2026-01-15T10:07:20Z",
    "total": "15.00",
    "subtotal": "15.00",
    "currencyCode": "USD",
    "orderPhone": "+13612221788",
    "orderItems": [
      {
        "id": "line_1",
        "quantity": "1",
        "productName": "Subscription",
        "productPrice": "15.00"
      }
    ],
    "transactionDetails": {
      "deviceIpAddress": "203.0.113.10"
    }
  },
  "merchantProfile": {
    "name": "Example Co",
    "merchantReceiptContact": {
      "emailForReceipt": "support@example.com",
      "phoneForReceipt": "+18005550100",
      "websiteForReceipt": "https://example.com"
    }
  },
  "accountProfile": {
    "name": { "givenName": "Jane", "familyName": "Doe" },
    "email": "jane@example.com",
    "accountBillingAddress": {
      "city": "Austin",
      "country": "US",
      "postalCode": "78701"
    }
  }
}
```

***

## 9. Idempotency and side effects

Treat lookups as **read-only** from Chargeblast’s perspective: avoid charging customers or mutating state solely on receipt of `digital_receipt.lookup` unless that matches your own product design.

***

## 10. SLA and deflection eligibility

<Warning>
  **1.5 second SLA** — If your endpoint does not respond in time, the lookup fails and the transaction may not be eligible for deflection or digital receipt display. Optimize matching logic for speed; see [Digital Receipts & Deflections](/guides/deflections-implementation).
</Warning>

If response times **repeatedly breach** this SLA, Chargeblast **emails the account owner** to flag that lookup latency is **consistently** over the allowed window—so you can fix performance or capacity before more lookups fail.

***

## Related

* [Digital Receipts & Deflections](/guides/deflections-implementation) — Pull vs push, qualifying fields, monitoring
* [Deflection Logs](/api-reference/deflections/logs) — Poll blocked chargebacks
* [Implementation Overview](/guides/implementation-overview) — Compare integration paths

***

*Describes behavior aligned with the Chargeblast application at the time of writing; verify against production if you depend on exact API guarantees.*
