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.
Record
Recommended purpose
Handling note
Internal send ID
Stable key generated by your application before the request.
Use it to make application retries traceable even before a provider message ID exists.
Provider message ID
Join key for callbacks, status lookup and support evidence.
Preserve as an opaque string; do not infer route or time from its shape.
Business reference
Verification, order, alert, campaign or case that caused the send.
Keep it separate from the recipient and avoid embedding sensitive data in identifiers.
Submission context
UTC 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 response
Evidence 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
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.
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 state
Incoming category
Example policy
Accepted
In progress
Advance and record both provider event time and receipt time.
Accepted or in progress
Delivered
Advance to delivered and emit idempotent downstream effects.
Accepted or in progress
Permanent failure
Advance to failed, retain raw reason and suppress blind automatic retry.
Delivered
Earlier in-progress event
Keep delivered as current, store the late event and increment an out-of-order metric.
Permanent failure
Contradictory delivered event
Apply a documented precedence rule or send to reconciliation; never discard the contradiction silently.
Any state
Unknown raw value
Keep 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.
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 stream
Core responsibility
Protection
send_records
One row per accepted or rejected send attempt and its business reference.
Transactional reducer, state version and last applied event reference.
delivery_outbox
Pending downstream actions produced by valid transitions.
Unique effect key, retry count, next attempt and terminal outcome.
mapping_versions
Auditable 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.
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.