Campaign Transmission Webhook

The Campaign Transmission Webhook is the endpoint you implement on your aggregator side. After Optimove orchestrates an SMS campaign, it sends the campaign to this webhook, at which point responsibility for transmitting the messages to end customers falls to your service.

This guide is for third-party developers building the endpoint that receives campaign requests from the SMS API (formerly OptiText API). Optimove forwards campaign message data to your Campaign Transmission Webhook, and your service is responsible for sending it on as SMS.

📘

Register this webhook's URL as the campaignSendUrl when you complete the Handshake Endpoint.

Request Format

Optimove sends POST requests to your endpoint with the following headers:

  • content-type: application/json
  • X-API-Key: The aggregator API key you registered during the handshake.
  • x-hub-signature: HMAC signature of the request body, formatted as <algorithm>=<hash>.
  • User-Agent: System user agent.

Request Body

{
  "batchId": "string",
  "metadata": {
    "appId": "int",
    "brandId": "string",
    "campaignId": "string",
    "campaignType": "int",
    "channelId": "int",
    "engagementId": "string",
    "isHashed": "boolean",
    "scheduledTime": "long",
    "tenantId": "int"
  },
  "recipients": [
    {
      "message": "string",
      "mobileNumber": "string",
      "customerId": "string"
    }
  ],
  "requestId": "string",
  "senderId": "string"
}
ParameterTypeMandatory/OptionalDescriptionHow it Affects Results
batchIdStringMandatoryUnique identifier for the message batch. Auto replies send an empty string ("").Use to group and track a single transmission batch.
metadata.appIdIntegerMandatoryApplication identifier.Return unchanged in delivery reports.
metadata.brandIdStringOptionalBrand identifier. Auto replies send an empty string ("").Return unchanged in delivery reports.
metadata.campaignIdStringOptionalCampaign identifier. Auto replies send an empty string ("").Return unchanged in delivery reports.
metadata.campaignTypeIntegerMandatoryType of campaign: 1 (Scheduled) or 2 (Triggered). Auto replies are sent as 2 (Triggered).Return unchanged in delivery reports.
metadata.channelIdIntegerMandatoryCommunication channel identifier.Return unchanged in delivery reports.
metadata.engagementIdStringOptionalUnique identifier for the customer engagement. Auto replies send null.Return unchanged in delivery reports when present.
metadata.isHashedBooleanMandatoryWhether the customer identifiers in this payload are hashed. You don't need to act on this value — just return it unchanged in delivery reports. Auto replies use false.Returned unchanged in delivery reports.
metadata.scheduledTimeLongMandatoryUnix timestamp (milliseconds) for when the message was scheduled to be sent as part of campaign execution. For auto-reply and triggered cases this may be a current/publish-time fallback rather than a future send time. This is not a time for your service to schedule sends against — transmit on receipt.Records the campaign execution time.
metadata.tenantIdIntegerMandatoryTenant identifier.Return unchanged in delivery reports.
recipientsArrayMandatoryArray of recipients with their personalized message.Each entry is one SMS to transmit.
recipients[].messageStringMandatoryThe message content to send.The body of the SMS.
recipients[].mobileNumberStringMandatoryRecipient's mobile phone number.The destination number.
recipients[].customerIdStringOptionalUnique customer identifier. Auto replies send an empty string ("").Use to correlate delivery reports.
requestIdStringMandatoryUnique ID per request to the webhook; use to deduplicate requests.Enables idempotent processing of retries.
senderIdStringMandatoryTenant's ID or phone number used to send the campaign.The SMS sender of record.
ℹ️

The metadata object should be returned unchanged in delivery reports. Optimove uses it to map incoming delivery reports back to tenant campaigns.

ℹ️

Auto replies

An auto reply is sent when a tenant has configured an automatic response to a keyword, and an inbound message arrives containing only that configured keyword. These requests arrive on this same webhook but aren't tied to a scheduled campaign, so several fields carry empty or default values: batchId, brandId, campaignId, and customerId are empty strings (""), engagementId is null, isHashed is false, and campaignType is 2 (Triggered). Handle them exactly like campaign messages — transmit each recipients[].message to its mobileNumber on receipt.

Sample Request

{
  "batchId": "batch-123",
  "metadata": {
    "appId": 123,
    "brandId": "test-brand",
    "campaignId": "232522",
    "campaignType": 1,
    "channelId": 493,
    "engagementId": "aaabbbccc",
    "isHashed": false,
    "scheduledTime": 1704106200000,
    "tenantId": 1
  },
  "recipients": [
    {
      "message": "Hello! Your order is ready for pickup.",
      "mobileNumber": "+1234567890",
      "customerId": "customer-001"
    },
    {
      "message": "Hello! Your order is ready for pickup.",
      "mobileNumber": "+1234567891",
      "customerId": "customer-002"
    }
  ],
  "requestId": "3f7c9a2e-6d14-4b81-9c52-2a9e7b6d4c10",
  "senderId": "test-sender"
}

Authentication

API Key

Each request includes an X-API-Key header containing the aggregator API key you registered. Validate this key on every request.

HMAC Signature Verification

Every request includes an x-hub-signature header containing an HMAC signature of the request body. Verifying this signature is critical for security.

The signature follows the format <algorithm>=<hash>, for example sha256=a1b2c3d4e5f6....

To verify a request:

  1. The payload is the raw request body exactly as received — the bytes on the wire. Do not re-serialize, reorder, or reformat the JSON before computing the signature.
  2. HMAC-SHA256 is computed over that payload using your shared secret key.
  3. The hash is lowercase hexadecimal, prefixed with sha256=.

Optimove signs with SHA256 only:

AlgorithmHash LengthUse Case
SHA25664 charactersThe only supported algorithm
import hmac
import hashlib

def verify_signature(payload, signature_header, secret_key):
    """Verify the HMAC signature from Optimove."""
    if not signature_header or "=" not in signature_header:
        return False

    algorithm, received_hash = signature_header.split("=", 1)

    computed_hmac = hmac.new(
        secret_key.encode("utf-8"),
        payload.encode("utf-8"),
        hashlib.sha256,
    )
    computed_hash = computed_hmac.hexdigest().lower()

    # Constant-time comparison to prevent timing attacks
    return hmac.compare_digest(computed_hash, received_hash)
⚠️

Always use a constant-time comparison (for example, hmac.compare_digest) when checking the signature, and compute the HMAC over the raw request body. Reject any request with a missing or invalid signature.

Response Requirements

Return any 2xx status code to indicate successful processing — 200 OK is standard. A response body is optional:

{
  "message": "Message processed successfully",
  "processedAt": "2024-01-01T12:30:00Z",
  "batchId": "batch-123"
}

Error Handling

Retryable Errors

Optimove retries requests for these status codes:

Status CodeUse Case
429Rate limit exceeded — retry with backoff.
500Internal server error — temporary issue.
503Service unavailable.

Network connectivity issues and request timeouts also trigger retries.

Non-Retryable Errors

Return 4xx codes for client errors. These are not retried: 400, 401, 403, 404, 405, 406, 409, 410, and 422. Any non-2xx status not listed as retryable is treated as non-retryable and aborts the campaign.

Error Response Body

{
  "error": "Invalid message format",
  "message": "The 'recipients' field is required",
  "code": "MISSING_RECIPIENTS"
}

Retry Behavior

  • Maximum retries: Requests are sent up to 5 times.
  • Retry interval: Exponential backoff (~10s, 20s, 30s+).
  • Dead letter handling: Failed messages go to a dead letter queue after the maximum attempts.
  • Timeout: Requests may time out and be retried.

Recommendations for your API:

  • Implement idempotency using requestId to handle duplicate requests.
  • Use circuit breakers to prevent cascading failures.
  • Implement graceful degradation for partial outages.
  • Return appropriate status codes to control retry behavior.

Security Considerations

  • Verify the HMAC signature before processing, using constant-time comparison.
  • Reject requests with invalid or missing signatures.
  • Store your HMAC secret securely (environment variables or a key management system).
  • Validate the API key on every request and rate-limit by key.
  • Use HTTPS-only endpoints and consider IP allowlisting Optimove's egress addresses.
    • For the IP addresses to allow, see the IP Allowlist reference.

Testing and Validation

Implement test cases for valid request processing (correct JSON, valid signature, correct API key), authentication failures (return 401/403), payload validation (missing fields → 400, business logic errors → 422), error conditions (5xx, 503), and edge cases (empty recipients array, large payloads, special characters in message content).

curl -X POST "https://your-endpoint.example.com/webhook" \
  -H "Content-Type: application/json" \
  -H "X-API-Key: <YOUR_API_KEY>" \
  -H "x-hub-signature: sha256=<SIGNATURE>" \
  -d '{
    "batchId": "test-batch-123",
    "metadata": {
      "appId": 123,
      "brandId": "test-brand",
      "campaignId": "test-campaign",
      "campaignType": 1,
      "channelId": 493,
      "engagementId": "test-engagement-456",
      "isHashed": false,
      "scheduledTime": 1704106200000,
      "tenantId": 1
    },
    "recipients": [{
      "message": "Test message",
      "mobileNumber": "+1234567890",
      "customerId": "test-customer"
    }],
    "requestId": "3f7c9a2e-6d14-4b81-9c52-2a9e7b6d4c10",
    "senderId": "test-sender"
  }'

Configuration Requirements

When registering your endpoint with the SMS API, provide:

  1. Endpoint URL: Your HTTPS webhook URL (campaignSendUrl).
  2. API Key: The unique key Optimove sends as X-API-Key.
  3. HMAC Secret: The shared secret for signature verification.
  4. HMAC Algorithm: Preferred algorithm (sha256 recommended).
  5. Sender ID: Your unique sender identifier.

After you receive and transmit a campaign, report status back via the Delivery Report Endpoint.