Skip to main content

Webhooks

Get real-time notifications when important events happen in the Seamless OS platform. Webhooks eliminate the need to poll the API,your endpoint receives an HTTPS POST immediately after subscriptions are created, payments complete, or usage thresholds are exceeded.

Quick start

Webhooks deliver complete resource snapshots using a simple, consistent format. Each event includes everything you need to update your systems or trigger business logic.

Basic integration

  1. Set up your endpoint to receive HTTPS POST requests
  2. Parse the JSON payload and extract the eventId for deduplication
  3. Return HTTP 200 to acknowledge receipt
  4. Process events asynchronously to avoid blocking webhook delivery

Essential pattern

app.post('/webhooks/telness', async (req, res) => {
  const { eventId, type, data } = req.body;

  try {
    // Atomic lock acquisition to prevent duplicate processing
    const acquired = await redis.setnx(`webhook:${eventId}`, 'processing');
    if (!acquired) {
      return res.status(200).send('OK'); // Already processed
    }

    // Set expiration in case of crash
    await redis.expire(`webhook:${eventId}`, 3600);

    // Queue event for async processing
    await eventQueue.add('process-webhook', { eventId, type, data });

    // Mark as completed only after successful queuing
    await redis.set(`webhook:${eventId}`, 'completed', 'EX', 86400);

    res.status(200).send('OK');
  } catch (error) {
    // Clean up on failure to allow retry
    await redis.del(`webhook:${eventId}`);
    console.error('Webhook processing error:', error);
    res.status(500).send('Internal Server Error');
  }
});

Event structure

All webhook payloads use the same envelope format with complete resource snapshots:
{
  "eventId": "8d7e6c5b-4a3f-2e1d-9c0b-112233445566",
  "type": "subscription.activated",
  "occurredAt": "2025-09-30T12:34:56Z",
  "data": {
    "subscriptionId": "123e4567-e89b-12d3-a456-426614174000",
    "status": "ACTIVATED",
    "customer": {
      "customerId": "987f6543-21cb-a0ed-654f-987654321000",
      "name": "Acme Corporation"
    },
    "productOffering": {
      "productOfferingId": "456a789b-cd12-34ef-567g-890123456789",
      "name": "Business Mobile Plan",
      "price": {
        "amount": 29.99,
        "currency": "USD"
      }
    },
    "subscriber": {
      "msisdn": "+46701234567",
      "email": "user@acme.com"
    },
    "createdAt": "2025-09-25T08:15:30Z",
    "updatedAt": "2025-09-30T12:34:56Z"
  }
}

Envelope fields

FieldDescription
eventIdUnique identifier for this logical event (stable across delivery retries)
typeDot-namespaced event identifier (domain.action)
occurredAtWhen the underlying business event happened
dataComplete snapshot of the affected resource at that moment

Connect API integration

Webhook payloads contain the same resource data available through the Connect API endpoints. The data object matches the schema you’d receive from GET requests, enabling seamless integration between real-time events and API calls. Example correlations:
  • subscription.created → GET /subscriptions/{subscriptionId}
  • paymentLink.expired → GET /payment-links/{paymentLinkId}
  • order.submitted → GET /orders/{orderId}
This consistency means you can use webhook data directly or fetch additional details using the embedded identifiers, depending on your integration needs.

Delivery guarantees

Reliability: At-least-once delivery with exponential backoff retries over approximately 24 hours for failed deliveries. Idempotency: Use the eventId field as your deduplication key. The same eventId will be sent for all retry attempts of the same logical event. Ordering: Events are not guaranteed to arrive in strict chronological order across different resource types. Within a single resource, events generally follow causal order, but your integration should handle events idempotently. Payload format: Typically includes the complete resource snapshot where applicable.

Implementation guide

  1. Parse and validate the JSON payload structure
  2. Check for duplicates using eventId before any processing
  3. Acknowledge immediately with HTTP 200 status
  4. Queue for async processing to handle business logic outside the webhook handler
  5. Perform side effects out-of-band (database updates, notifications, etc.)

Idempotency best practices

  • Use eventId as your deduplication key - it’s stable across retries
  • Check before processing - always verify if the event was already handled
  • Mark as processed only after success - prevents dropped events on failures
  • Set TTL on locks - prevents stuck locks from crashes or timeouts
  • Clean up on failure - remove locks to allow retries after errors
For additional idempotency patterns and implementation details, see the Idempotency guide.

Error handling

We only inspect the HTTP status code—the response body is ignored. Return HTTP 2xx status codes only after successfully queuing or processing the event. Non-2xx responses trigger automatic retries with exponential backoff. Best practices:
  • Return 200 for successfully processed events (including duplicates)
  • Return 4xx for malformed payloads (stops retries)
  • Return 5xx for temporary failures (triggers retries)
  • Response body content is not read or processed

Troubleshooting

Missing events: Verify your endpoint returns HTTP 200 and processes requests quickly (under 10 seconds). Duplicate processing: Always check eventId before performing non-idempotent operations. Event ordering: Design your integration to handle events in any order, don’t rely on strict chronological delivery. Large payloads: Events include complete resource snapshots, which may be substantial for complex orders or subscriptions.

Anything missing?

We’re always looking to expand our platform. If there’s a webhook event or feature you’d like to see, please get in touch!
I