You can now set up a receipt / proof-of-purchase cashback flow powered by our partner Hashting: a member uploads a receipt through a Qualifio campaign, Hashting runs OCR and validates it, and the result is shown back to the member in the portal — with points granted automatically once the receipt is accepted.
This article explains how the pieces fit together, how to configure the flow for a client, what the receipt statuses mean, and how to troubleshoot a receipt that isn't updating.
What's new?
This ships on top of three building blocks that already exist in the platform — nothing receipt-specific was hardcoded:
- An Interaction — a generic loyalty-profile record used to show a member the current + historical status of something happening outside Qualifio (here, a receipt). It's rendered through a Handlebars detail template inside an Interaction widget in the portal.
- An incoming event — the generic endpoint + JSONata mapping Qualifio already uses to receive any partner's webhook. For Hashting, one mapping feeds the earning rule (the points side), and a second mapping (the "Interaction JSONata") writes the Interaction record the member sees.
- A Hashting Cashback integration on the campaign — the campaign that collects the receipt image pushes it to Hashting, and a push rule on that campaign keeps it wired to the flow.
Because it's built on generic Interaction + incoming-event mechanics, this isn't limited to Hashting — the same pattern works for any partner that reports back an asynchronous status.
How does a receipt flow from upload to points?
- The member opens the portal and clicks Upload a receipt, which opens the embedded Qualifio campaign pre-filled with their first name, last name and email.
- They upload the receipt image (mapped to the campaign's
invoicefield) and submit. - The campaign pushes the submission to Hashting via the Hashting Cashback Flow push rule.
- Hashting calls back the incoming event as it processes the receipt — first with a Pending update, then a final Accepted (with the line items and amount) or Refused (with a reason) update.
- Each callback is mapped twice: the event-mapping JSONata feeds the earning rule, and the Interaction JSONata upserts the Interaction record shown in the portal.
- Once a receipt is Accepted, the earning rule (configured to trigger on this event, using Total Amount (VAT Included)) grants the points.
Interaction vs. incoming event vs. Transaction — what's the difference?
These three concepts sit next to each other in this flow, so here's a quick comparison:
| Concept | What it is | Where it's configured | Member-facing? |
|---|---|---|---|
| Incoming event | The generic endpoint + JSONata mapping that receives a partner's webhook (Hashting or any other) | Once per integration, in the incoming-event admin | ❌ No — plumbing only |
| Interaction | A record showing a member the current + historical status of an external process, rendered via a Handlebars template | Created once per use case (e.g. "Store receipt"), populated by the incoming event's Interaction JSONata | ✅ Yes — shown in an Interaction widget |
| Transaction | The record created by the earning rule when points are actually granted | Created automatically by the earn flow | Indirectly — it's what moves the points balance |
An incoming event can exist without an active interaction (points only, no status display); an Interaction always depends on an incoming event to feed it.
How do I set up the Hashting Cashback flow for a client?
Our internal Professional Services team takes care of the technical configuration (steps 1 to 6 below) on your behalf. If interested in this service, please get in touch with Professional Services (integrations@qualifio.com) to set it up.
- Create the Interaction (type "Store receipt"), using the standard Handlebars detail template — it renders the current status, the refusal reason when present, the itemised amount, a link to view the receipt, and the status history.
- Create the incoming event: link it to the Hashting integration and to that Interaction, set the authentication (currently a token in a query parameter), and set Source = Ecommerce / Action = Purchase. Add the two JSONata mappings — the earn mapping and the Interaction mapping (Qualifio provisions the standard Interaction snippet; it isn't exposed to the client).
- Subscribe the webhook on Hashting's side — a one-off API call to Hashting registering your incoming event's URL as the callback for
cashback_entry_status_update_with_data. - Create a Hashting Cashback integration in the campaign module.
- Build the campaign that pushes to Hashting, mapping
firstname,lastname,email, and the receipt upload to theinvoicefield. - Activate the "Hashting Cashback Flow" push rule on that campaign.
- Add the widgets to the portal: a Campaign widget embedding the upload campaign, and an Interaction widget pointing at the receipt Interaction.
- Create the earning rule that grants points when a receipt is accepted, using Total Amount (VAT Included) as the awarded amount.
Pricing:
- Technological fee for feature activation: €2,000
- Setup fee on the Qualifio side: €3,000
What do the receipt statuses mean?
- Pending — the receipt was submitted and Hashting hasn't returned a final result yet.
- Accepted — Hashting validated the receipt and sent back the detailed line items and amount. This is also the trigger point for the earning rule to grant points.
- Refused — Hashting rejected the receipt. When Hashting provides a reason, it's shown directly in the Interaction's title and history — no separate lookup needed.
Why is a receipt stuck on "Pending", or not earning points?
Check, in order:
- The webhook subscription on Hashting's side points at the right incoming-event URL and token — a stale or mistyped one means Hashting has nothing to call back.
- The push rule ("Hashting Cashback Flow") is active on the campaign — if it's off, the receipt image never reaches Hashting in the first place.
- The Interaction JSONata still outputs the required
externalId,label,status, anddatakeys — a broken mapping updates nothing, but never blocks or mis-grants points, since the points path is gated in code, not in the mapping. - The earning rule is active and correctly reads the amount from the Accepted payload (
Total Amount (VAT Included)). - Hashting's own processing — if neither the itemised payload nor a
rejectedstatus has come back yet, the receipt legitimately stays Pending until Hashting finishes reviewing it.
Can I reuse this for another partner or event type?
Yes. Nothing in the Interaction or incoming-event configuration is Hashting-specific — the same pattern (incoming event + Interaction JSONata + Interaction widget) applies to any partner that reports an asynchronous status back to a member-facing process.