Skip to Content
DevelopersEventsReceipt events

Receipt events

“Receipt” inside Active Reach covers two related but distinct things:

  1. Channel delivery receipts — the sent / delivered / opened / clicked / failed callbacks that channel providers fire after a message goes out. These are PASSIVE for MAU classification.
  2. Bill receipts — customer purchase records written to the unified bill_receipts table by Active Bill, the always-on 7th Active product.

Both are documented below.

1. Channel delivery receipts

When Active Reach sends a message, the channel provider reports back delivery status — “delivered”, “bounced”, “opened”, etc. These are delivery receipt events.

Active Reach sends message Channel provider (WhatsApp, SendGrid, Twilio, MSG91, etc.) Provider fires webhook to Active Reach Event ingress processes and normalizes Receipt event appears in: - Contact timeline (workspace-scoped via __org__ sentinel or workspace_id) - Campaign analytics - Live events stream - Outbound webhooks (if subscribed) — see /developers/webhooks/event-types

The canonical event name on the bus is channel_delivery_status, plus dot-namespaced per-channel variants (email.delivered, whatsapp.read, etc.). All are classified PASSIVE and excluded from MAU.

Per-channel statuses

WhatsApp

StatusWhen it fires
sentMessage accepted by WhatsApp servers
deliveredMessage delivered to the recipient’s device
readRecipient opened the conversation (blue ticks)
failedPermanent failure (invalid number, blocked, template rejected)

Email

StatusWhen it fires
sentMessage accepted by the ESP
deliveredESP confirmed delivery to recipient’s mail server
bouncedHard bounce (invalid address) or soft bounce (mailbox full)
openedRecipient opened the email (tracking pixel loaded)
clickedRecipient clicked a tracked link
complainedRecipient marked as spam
unsubscribedRecipient clicked the unsubscribe link

SMS

StatusWhen it fires
sentMessage accepted by the SMS gateway
deliveredCarrier confirmed delivery to handset
failedPermanent failure (invalid number, DND, carrier rejection)

RCS

StatusWhen it fires
sentMessage accepted by the RCS gateway
deliveredMessage delivered to the device
readRecipient opened the message
failedDelivery failed (fallback to SMS if configured)

Push

StatusWhen it fires
sentPush dispatched to FCM/APNs
deliveredDevice confirmed receipt
clickedUser tapped the notification

Normalized payload

Regardless of channel, delivery receipts normalize to a common shape. Since the workspace-scoped delivery substrate rolled out (2026-05-22), workspace_id is reliably present — either the brand’s UUID or the __org__ sentinel for intentionally org-level rows.

{ "type": "delivery", "status": "delivered", "message_id": "msg_xyz789", "campaign_id": "camp_welcome_q2", "contact_id": "usr_456", "channel": "whatsapp", "timestamp": "2026-05-27T10:31:15.000Z", "provider": "meta", "workspace_id": "ws_abc123", "metadata": {} }

Providers carry the workspace_id round-trip via three tiers: echoed metadata (MSG91 variables, SendGrid custom_args, Razorpay notes, Cashfree subscription_tags), URL-encoded callback paths (POS, shipping, Shopify), or a DB lookup fallback (Stripe, Clerk).

2. Bill receipts (Active Bill)

Active Bill is the 7th Active product and the only one that is always-on. Every tenant has bills the moment any other Active product fires — Active POS, Active Commerce, Active Chat, Active Loyalty, Active Rewards, and Active Feedback all generate purchase records via the unified bill_receipts table.

A short code on the row resolves to the customer-facing web page:

actii.me/b/{short_code}

This is the canonical customer bill URL.

Bill PDFs through the CDN

Bill and invoice PDFs serve through media.active-reach.ai (Cloudflare fronts both the aegis-media and aegis-data-v2 buckets). Customer surfaces must use the canonical helpers:

  • canonical_receipt_url(s3_key) — customer bill PDFs
  • canonical_invoice_url(s3_key) — operator GST invoices

Never embed raw E2E presigned URLs in customer surfaces.

Brand canon

The bill PDF wordmark, customer-facing subjects, and the storefront UI say Active Reach Platform (product) and Active Reach Intelligence LLP (company). The Aegis name and the AEG/<FY> invoice series prefix stay in internal code paths, env vars, DB tables, and the operator-facing invoice series.

Tax line-item rounding

For bundled bills where the gross is the meaningful number (rather than a per-unit price), TaxInvoiceService requires quantity = 1 with the back-computed taxable placed into unit_amount_paise. The substrate ignores any explicit total_paise on TaxLineItem inputs and recomputes taxable = quantity × unit_amount_paise — so a quantity × unit_amount shape that doesn’t multiply out to the intended gross silently loses paise.

What’s next