Skip to main content

Developer implementation guide

Build a reliable delivery-report pipeline

Treat each delivery callback as an untrusted, retryable event that may arrive late, repeat or appear out of order. Persist the send response first, validate the exact callback contract from the current API documentation, and make every state transition replay-safe.

Payloads and pseudocode below are provider-neutral design examples. They do not define TextPulse endpoint names, required fields, authentication headers, retry timing or status enums.

Reference architecture

Decouple callback receipt from business processing

A webhook endpoint should perform a small amount of deterministic work: authenticate, enforce basic limits, preserve the received event and hand it to a durable queue or transaction. Reporting, notifications and business actions can run asynchronously.

1. SubmitStore request and message ID
2. ReceiveCapture raw callback bytes
3. VerifyApply documented authentication
4. PersistInsert event idempotently
5. ReduceApply a valid state transition
6. ObserveUpdate metrics and workflows

Synchronous boundary

Limit body size, capture the exact bytes required by the documented verification method, validate freshness where supported, parse defensively and commit the receipt. Avoid carrier lookups, customer notifications or expensive analytics before acknowledgment.

Asynchronous boundary

Normalize the status, apply the state machine, update derived metrics, trigger approved business actions and route unrecognized events to review. A replay should produce the same final database state without sending duplicate downstream effects.

Message identity

Persist the send response as the root record

The delivery pipeline cannot join a callback to a business workflow if the message identifier exists only in a transient application log. Store the send attempt and response in one durable transaction whenever practical.

RecordRecommended purposeHandling note
Internal send IDStable key generated by your application before the request.Use it to make application retries traceable even before a provider message ID exists.
Provider message IDJoin key for callbacks, status lookup and support evidence.Preserve as an opaque string; do not infer route or time from its shape.
Business referenceVerification, order, alert, campaign or case that caused the send.Keep it separate from the recipient and avoid embedding sensitive data in identifiers.
Submission contextUTC time, destination country, sender, template version and encoding context.Mask recipient values in broad analytics and keep plaintext message bodies out of routine logs.
Raw responseEvidence for contract changes and incident review.Redact secrets and apply a retention policy; do not treat logs as the primary database.
Provider-neutral storage sketchAdapt to the current documented response
send_record = {
        internal_id: generated_before_submit,
        provider_message_id: "<OPAQUE_MESSAGE_ID>",
        business_reference: "<INTERNAL_WORKFLOW_ID>",
        submitted_at_utc: "<TIMESTAMP>",
        current_state: "accepted",
        current_state_at_utc: "<TIMESTAMP>",
        raw_response_redacted: "<REDACTED_EVIDENCE>"
      }

Event contract

Validate documented fields and preserve unknown ones

Build the parser from the current account-specific reference and representative sanitized payloads. Required-field validation protects the state machine; retaining a redacted raw event helps diagnose new optional fields or status values without losing evidence.

Identity fields

Locate the message identifier and any event identifier defined by the contract. If no event ID exists, construct a conservative deduplication fingerprint from stable documented fields rather than the order of JSON keys.

Status fields

Keep the raw status and reason exactly as received. Map known values to an internal enum, send unknown values to a review queue and never silently classify an unfamiliar failure as delivered.

Time fields

Parse documented timestamps with timezone information, store UTC and distinguish provider event time from your receipt time. Both are needed to detect delay, clock anomalies and queue backlog.

Recipient context

Use a masked or keyed form for routine correlation. Avoid propagating a full phone number into metrics labels, traces, error strings or ticket titles.

Version context

Record a documented event version or your parser version when available. Schema validation and fixtures should make an incompatible contract change visible before it corrupts status history.

Raw evidence

Store the minimum redacted representation needed for audit and replay. If authentication requires exact raw bytes, verify before transforming or reserializing the payload.

Webhook security

Authenticate before trusting a delivery event

Use the current TextPulse callback-security instructions as the authority. The controls below describe the questions an implementation should resolve without assuming a particular header, signature algorithm or source-address list.

01

Transport

Require HTTPS, a valid certificate and a narrowly exposed route. Apply request-size, connection and rate limits that can absorb legitimate retry bursts without allowing unbounded bodies or slow requests.

02

Authenticity

Implement the documented secret, token, signature or mutual-authentication method exactly. Compare secrets in a timing-safe manner where relevant and rotate them through a controlled deployment path.

03

Replay control

When the contract provides event identity or signed time, enforce a documented freshness window and store seen identifiers. Network allowlists can be supplementary but should not replace message-level verification when one is specified.

04

Failure response

Reject unauthenticated traffic without exposing parsing detail, record a security metric and keep sensitive payloads out of alert text. Do not acknowledge an event that was neither verified nor durably preserved.

Idempotency

Assume valid callbacks can be delivered more than once

Retries are a normal reliability mechanism. The webhook consumer must make repeated receipt harmless at the database, queue and downstream-action layers.

Transactional reducer pseudocodeConceptual logic, not an API payload
processDeliveryEvent(raw_request):
        authenticated = verifyUsingCurrentDocumentation(raw_request)
        if not authenticated:
          reject

        event = parseAndValidate(raw_request)

        begin transaction
          if delivery_events already contains event.dedupe_key:
            commit and acknowledge

          insert delivery_event and redacted evidence
          message = lock send_record by provider_message_id

          if transitionAllowed(message.current_state, event.normalized_state):
            update message state and state time
            enqueue unique downstream effects
          else:
            record ignored_or_review transition
        commit

        acknowledge

Database uniqueness

Back deduplication with a unique constraint, not an in-memory cache alone. Multiple worker instances can receive the same retry concurrently.

Effect uniqueness

Use an outbox or unique business-action key so a repeated delivered event does not issue two refunds, two notifications or two verification state changes.

Acknowledgment timing

Return the documented success response only after durable acceptance. If processing continues asynchronously, the queue or database commit becomes the acknowledgment boundary.

State machine

Make transition rules explicit and reviewable

Events may arrive out of order because different systems and networks finalize at different times. A state reducer should protect terminal outcomes, retain contradictory evidence and make manual reconciliation possible.

Current stateIncoming categoryExample policy
AcceptedIn progressAdvance and record both provider event time and receipt time.
Accepted or in progressDeliveredAdvance to delivered and emit idempotent downstream effects.
Accepted or in progressPermanent failureAdvance to failed, retain raw reason and suppress blind automatic retry.
DeliveredEarlier in-progress eventKeep delivered as current, store the late event and increment an out-of-order metric.
Permanent failureContradictory delivered eventApply a documented precedence rule or send to reconciliation; never discard the contradiction silently.
Any stateUnknown raw valueKeep the current normalized state, preserve the event and alert the contract-review owner.

The exact terminal and precedence rules must match the current provider contract and your business risk. The table is an implementation review aid, not a TextPulse status specification.

Ordering and time

Store event time and receipt time separately

Sorting only by arrival time can create false regressions; trusting remote event time without validation can also be unsafe. Preserve both clocks, then apply the documented state precedence and an explicit tolerance for late events.

Provider event time

Represents when the upstream system says the status occurred. Parse timezone-aware values and retain the original string if exact replay or troubleshooting may be needed.

Webhook receipt time

Generated by your ingress service when the request arrived. It measures callback transport delay and distinguishes provider delay from your own queue or worker backlog.

State application time

Generated when the reducer committed the accepted transition. It supports processing-lag alerts and proves whether a delayed dashboard is caused by ingestion or downstream computation.

Business action time

Records when the application reacted, such as closing an alert or offering a fallback. Keep this separate so callback receipt is not mistaken for product completion.

Reconciliation

Plan for missing, delayed and contradictory events

A production pipeline needs a bounded process for accepted messages that never reach a final state. Use only the status-retrieval or support mechanism documented for the account; do not invent polling endpoints or scrape dashboards.

Age buckets

Count non-final messages by age and use-case deadline. A five-minute-old OTP and a five-minute-old low-priority notification have different urgency.

Documented lookup

If the current API provides status retrieval, call it with rate limits, backoff and a finite reconciliation window. Store the lookup source separately from webhook evidence.

Support evidence

If no automated lookup applies, prepare message IDs, UTC times, destinations, sender context and raw statuses for the authenticated support path.

Contradiction queue

Route impossible transitions, mismatched identifiers and unknown reason values to a review queue with owner, deadline and resolution note.

Backfill safety

Run replay and backfill through the same idempotent reducer used for live callbacks. Do not update current state with an unreviewed bulk SQL shortcut.

Closure rule

Define when unresolved messages leave the active queue, how they appear in metrics and whether a later receipt can reopen the record.

Storage design

Keep immutable events and a derived current state

An append-oriented event table preserves evidence; a separate current-state record makes product queries efficient. Rebuild the derived state through the same reducer when mappings change.

Table or streamCore responsibilityProtection
send_recordsOne row per accepted or rejected send attempt and its business reference.Unique internal ID, indexed provider ID, masked recipient, controlled content storage.
delivery_eventsImmutable normalized and raw-redacted callback evidence.Unique dedupe key, event and receipt times, parser version, authentication result.
message_stateCurrent normalized status for application reads.Transactional reducer, state version and last applied event reference.
delivery_outboxPending downstream actions produced by valid transitions.Unique effect key, retry count, next attempt and terminal outcome.
mapping_versionsAuditable raw-status and reason normalization rules.Version, effective date, reviewer and regression fixtures.

Observability

Monitor the consumer as well as SMS outcomes

A fall in finalization may be a route issue, but it may also be webhook authentication failure, queue backlog, parser rejection or a stalled reducer. Separate provider-facing and consumer-facing signals.

Ingress health

Request volume, response class, authentication failure, body rejection and rate limiting. Alert on sustained change rather than one hostile request.

Queue health

Oldest event age, depth, enqueue failure, worker throughput and dead-letter count. Use event age to detect backlog even when average latency looks normal.

Parser health

Unknown status, missing required field, invalid time, unmapped reason and schema-version distribution. Preserve sanitized examples for contract review.

Reducer health

Applied transitions, duplicates, ignored late events, contradictions and lock contention. A spike in rejected transitions can reveal ordering or mapping defects.

Outcome health

Acceptance, finalization, delivery, failure composition and time-to-final-state by destination, sender, template and traffic purpose.

Business health

Verification completion, resend, customer contact and fallback activation. Keep user outcomes separate from transport delivery.

Testing

Test duplicates, reordering and failure paths before launch

A single happy-path callback proves little. Build sanitized fixtures from the documented contract and exercise the same endpoint, persistence and reducer used in production.

Contract fixtures

Known valid statuses, each documented failure family, optional fields, unknown fields, malformed payloads and timezone variants.

Security tests

Missing or invalid authentication, altered raw body, stale replay, oversized request, wrong content type and rotated credentials according to the supported method.

Reliability tests

Duplicate delivery, retry after timeout, concurrent workers, queue interruption, database deadlock and process crash between event insert and acknowledgment.

Ordering tests

Delivered before in-progress, failure after delivery, unknown status between known events and a late final receipt after the reconciliation window.

Backfill tests

Replay the same event set twice and confirm identical current state, metrics and downstream effects. Compare row counts and effect keys, not only HTTP responses.

Privacy tests

Confirm logs, traces, metrics, error alerts and dead-letter payloads do not expose secrets, full recipient lists or plaintext OTP values.

Release plan

Deploy the pipeline in observable stages

Separate ingestion from business automation during initial rollout. First prove that authenticated events are durable and correctly reduced; then enable customer-facing effects behind reversible controls.

1. ShadowReceive and store only
2. CompareValidate mappings and counts
3. AlertEnable internal signals
4. AutomateRelease bounded effects
5. ReconcileProcess missing states
6. ReviewRetire temporary controls

Go-live checks

  • Current callback contract reviewed
  • Secrets configured outside source code
  • Unique dedupe constraint active
  • Unknown-status alert has an owner
  • Queue age and dead-letter alerts tested
  • Rollback disables effects without losing events

Change checks

  • Parser and mapping versions recorded
  • Sanitized fixtures cover new fields
  • Replay produces no duplicate effects
  • Dashboard definitions remain comparable
  • Support runbook reflects the current schema
  • Retention applies to temporary debug data

FAQ

Delivery-report implementation questions

Can my webhook handler process the business action before responding?

It can, but long synchronous work increases timeout and duplicate-delivery risk. Prefer authenticating and durably recording or enqueueing the event first, then process effects asynchronously through idempotent workers.

What should I use as the deduplication key?

Use the documented event identifier when one is guaranteed. Otherwise create a conservative fingerprint from stable contract fields and retain the raw evidence. Back the decision with a database unique constraint and collision review.

Should a late in-progress event overwrite delivered?

Normally a lower-precedence late event should be stored but should not regress a terminal current state. The exact precedence must match the current provider contract, and contradictions should remain visible for reconciliation.

How do I know which callback header or signature to verify?

Use the current TextPulse API or account documentation. This guide deliberately does not invent a header name or algorithm. Capture the raw request in the form required by that documented method before parsing changes the bytes.

How should I investigate accepted messages with no final callback?

Group them by age, use case and destination, verify consumer health, then use only the documented status lookup or authenticated support route. Keep unresolved states separate in reporting rather than guessing a final outcome.

Ship a replay-safe status consumer

Bind every event to a durable message ID, authenticate it, reduce it through explicit rules and prove that retries cannot duplicate customer effects.

Continue with API integration