Back to the journal
Momo Business Guides

Handle API webhook retries without duplicate business actions

Verify the correct Momo webhook signature, persist events before acknowledging them, and prevent repeated deliveries from creating duplicate actions.

A webhook delivery can succeed at your server while its acknowledgement never reaches the sender. If you create an invoice or send a message each time the HTTP request arrives, a retry can repeat the business action.

Build the receiver around verified events and durable processing. Momo Business has two customer webhook systems, so first identify which one you configured. Their signatures, payloads and retries are different.

Identify the contract before writing code

Contract Communication webhooks Automation webhook subscriptions
Configuration Communications Webhooks Automations event subscription
Signature header X-Signature X-Momo-Signature
Signed content Raw JSON body Timestamp, a dot, then raw JSON body
Payload identity Event-specific fields such as message_id Stable event id plus subscription identity
Delivery attempts One attempt, 10-second timeout Up to six attempts, 15-second HTTP timeout

Communication callbacks include flat message, order or group event fields. Automation callbacks wrap the business payload in data, with id, event, subject, actor and subscription alongside it. Do not use the same parser merely because both bodies contain an event name.

Read the communication webhook contract and automation contract for complete field definitions. Ordinary SMS and WhatsApp send requests do not gain idempotency simply because your webhook receiver deduplicates events.

Prepare the receiver and signing secret

Use a public HTTPS receiver, a durable datastore and a background worker. Keep signing secrets in server configuration, outside source control and request logs. An API key is not a webhook secret.

For automation subscriptions, an authorized manager can open Automations, edit the relevant webhook subscription and use Signing secret. Provision that value securely in the receiver. Communication webhook secret retrieval is not exposed through the customer screen or REST API; arrange provisioning with the responsible administrator before enforcing signatures.

For communication callbacks, compare X-Signature with the lowercase hexadecimal HMAC-SHA256 of the exact raw body. There is no sha256= prefix. For automation callbacks, parse t and v1 from X-Momo-Signature and verify a signed timestamp as well:

import hashlib
import hmac
import time

def verify_automation(header, raw_body, secret):
    try:
        parts = dict(part.strip().split("=", 1)
                     for part in header.split(","))
        timestamp = int(parts["t"])
        if timestamp <= 0 or abs(time.time() - timestamp) > 300:
            return False
        signed = str(timestamp).encode() + b"." + raw_body
        expected = hmac.new(secret.encode(), signed,
                            hashlib.sha256).hexdigest()
        return hmac.compare_digest(expected, parts["v1"])
    except (KeyError, ValueError, TypeError, OverflowError):
        return False

Pass the original request bytes as raw_body and the configured secret as text. Verify before decoding JSON. Re-encoding parsed JSON can change whitespace, Unicode escaping or key order. Keep the server clock accurate for the five-minute timestamp tolerance.

Accept once, process reliably

For an automation callback, use the subscription ID and event ID together as a unique inbox key. In one local database transaction, insert the verified event and a pending work item. Enforce uniqueness in the database so two simultaneous deliveries cannot both win.

If the event is already durably stored, acknowledge the duplicate without inserting another work item. Return a 2xx only after storage succeeds. Let the worker process pending items separately and record completion. If storage fails, return an error; do not acknowledge data you have discarded.

Communication callbacks do not supply the same stable event UUID. Store a digest to recognize byte-identical repeats, but make the actual action conditional on the resource and transition. A different callback timestamp can change the digest. For example, update a known message's delivery state harmlessly instead of sending a new confirmation on every status callback.

Protect the business action too

Suppose the same automation event arrives twice because the first response timed out. The unique inbox key leaves one pending action. If the worker crashes after calling another service but before recording completion, that external action can still repeat. Use the destination service's idempotency mechanism when available, or reconcile its existing result before trying again.

Keep the business reference with the work item. Do not turn an uncertain send into a fresh SMS POST automatically. Retain the returned message ID and read its current state before deciding whether another send is needed.

Plan recovery around actual retries

Automation deliveries retry transport failures and HTTP 408, 429 and 5xx. Default delays are 10, 20, 40, 80 and 160 seconds. A positive numeric Retry-After can override the next delay, capped at 300 seconds. Other refusals, including invalid authentication, are terminal. Inspect subscription errors and enabled state after failures.

Communication callbacks have no automatic retry or replay log. Poll important message or order records to reconcile missing updates. Test your receiver offline with a valid signature, a changed body, an old timestamp, two concurrent copies and a worker restart. Each test should leave one intended business action and enough recorded context to explain the result.

Thanks for reading.Explore more stories