Skip to content

Handling Webhooks

Learn how to receive and process webhook events from Sesamy.

Overview

Sesamy sends webhook notifications to your configured endpoints when specific events occur. Webhooks are designed to be lightweight, containing only essential identifiers. Your application should use these identifiers to fetch the full details from the Sesamy API.

Built-in Webhook Forwarding

If you want Sesamy to forward events to your endpoint without building a custom consumer, you can use the built-in webhook integration. Provide the following to your Sesamy account manager:

FieldTypeDescription
urlstringYour endpoint URL
headersobject(Optional) Custom HTTP headers for authentication (e.g., Authorization: Bearer token)

The built-in integration forwards six of the eight standard event types (all except entitlement.1#new and entitlement.1#update).

Retry behaviour: HTTP 5xx responses, HTTP 429, and network-level errors (connection refused, timeout, DNS failure, connection reset) are retried. HTTP 4xx responses other than 429 are treated as permanent failures and the event is not redelivered -- a 404 or 401 means the endpoint is misconfigured, and retrying will not fix it.

Retries are not exponential backoff: a retryable failure is delivered up to 3 times in total -- the initial attempt plus 2 redeliveries -- roughly two minutes apart, after which the event moves to a dead-letter queue and is logged for investigation.

Because a failure can be redelivered, your endpoint may see the same event more than once. Return a 2xx as soon as you have accepted the payload, process asynchronously, and make that processing idempotent (see Handle Idempotency).

See Integration Concepts for full retry details.

The rest of this page covers how to build your own webhook consumer for more advanced use cases.

Webhook Request Format

Webhooks are sent as HTTP POST requests with the following structure:

json
{
  "eventType": "userVendor.1#new",
  "vendorId": "vendor_123",
  "userId": "user_456",
  "contractId": "contract_789",
  "entitlementId": "entitlement_012",
  "checkoutId": "checkout_345",
  "occured_at": "2025-11-10T12:00:00Z"
}

Payload Fields

FieldTypeDescription
eventTypestringThe type of event that occurred (see Event Types below)
vendorIdstringThe vendor identifier
userIdstringThe user identifier
occured_atstringISO 8601 timestamp of when the event occurred
contractIdstring(Optional) Contract identifier, if applicable
entitlementIdstring(Optional) Entitlement identifier, if applicable
checkoutIdstring(Optional) Checkout identifier, if applicable

Event Types

The following webhook events are available. For a comprehensive reference of events, tags, and retry behaviour used by built-in integrations, see Integration Concepts.

Event TypeDescription
userVendor.1#newA new user-vendor relationship was created
userVendor.1#updateA user-vendor relationship was updated
contract.1#purchasedA contract was purchased
contract.1#updateA contract was updated
contract#cancelRequestA cancellation request was made for a contract
entitlement.1#newA new entitlement was created for a user
entitlement.1#updateAn existing entitlement was updated
checkout.1#confirmA checkout was confirmed

Note: entitlements created as part of a bundle (origin: BUNDLE) do not raise entitlement.1#new or entitlement.1#update events. They are filtered out before reaching any integration.

Setting Up Webhook Endpoints

Your webhook endpoint should:

  1. Accept POST requests
  2. Respond quickly (within a few seconds)
  3. Return a 2xx status code to acknowledge receipt
  4. Process the webhook asynchronously if needed

Example Endpoint

javascript
app.post('/webhooks/sesamy', async (req, res) => {
  const { eventType, vendorId, userId, contractId, occured_at } = req.body;

  // Acknowledge receipt immediately
  res.status(200).send('OK');

  // Process asynchronously
  processWebhook({ eventType, vendorId, userId, contractId, occured_at });
});

Fetching Full Event Data

Webhooks contain minimal data to reduce payload size. Use the provided identifiers to fetch complete information from the Sesamy API:

javascript
async function processWebhook({ eventType, vendorId, userId, contractId, checkoutId }) {
  switch (eventType) {
    case 'contract.1#purchased':
    case 'contract.1#update':
      // Fetch contract details
      const contract = await sesamyClient.getContract(contractId);
      // Process contract data
      break;

    case 'userVendor.1#new':
    case 'userVendor.1#update':
      // Fetch user-vendor relationship
      const userVendor = await sesamyClient.getUserVendor(vendorId, userId);
      // Process user data
      break;

    case 'checkout.1#confirm':
      // Fetch checkout details
      const checkout = await sesamyClient.getCheckout(checkoutId);
      // Process checkout data
      break;
  }
}

Best Practices

1. Validate Webhook Authenticity

Sesamy does not currently sign webhook payloads. There is no signature header to verify and no signing secret to request.

Authenticate deliveries using the custom headers you supply when configuring the endpoint -- Sesamy sends them verbatim on every request. A shared secret works well here:

javascript
app.post('/webhooks/sesamy', (req, res) => {
  if (req.get('Authorization') !== `Bearer ${process.env.SESAMY_WEBHOOK_SECRET}`) {
    return res.status(401).send('Unauthorized');
  }
  // ...
});

Your endpoint url must use https://. These headers are sent on every delivery, so an http:// endpoint would put your shared secret on the wire in clear text.

Treat the payload as untrusted regardless: it carries only identifiers, so read the authoritative record back from the Sesamy API by ID before acting on it.

2. Handle Idempotency

Webhooks may be delivered more than once. Ensure your processing logic is idempotent by tracking processed event IDs.

javascript
const processedEvents = new Set();

async function processWebhook(payload) {
  const eventId = `${payload.eventType}-${payload.occured_at}-${payload.userId}`;

  if (processedEvents.has(eventId)) {
    return; // Already processed
  }

  // Process the webhook
  // ...

  processedEvents.add(eventId);
}

3. Implement Retry Logic

If fetching data from the Sesamy API fails, implement exponential backoff retry logic:

javascript
async function fetchWithRetry(apiCall, maxRetries = 3) {
  for (let i = 0; i < maxRetries; i++) {
    try {
      return await apiCall();
    } catch (error) {
      if (i === maxRetries - 1) throw error;
      await new Promise((resolve) => setTimeout(resolve, Math.pow(2, i) * 1000));
    }
  }
}

4. Log All Webhooks

Keep a log of all received webhooks for debugging and auditing:

javascript
app.post('/webhooks/sesamy', async (req, res) => {
  // Log the webhook
  logger.info('Webhook received', {
    eventType: req.body.eventType,
    timestamp: req.body.occured_at,
    payload: req.body,
  });

  res.status(200).send('OK');
  processWebhook(req.body);
});

Testing Webhooks Locally

To test webhooks during development:

  1. Use a tool like ngrok to expose your local endpoint
  2. Ask your Sesamy account manager to point your test vendor's webhook endpoint at the ngrok URL (see Built-in Webhook Forwarding)
  3. Trigger events in your test environment
  4. Monitor the webhook delivery in your local application
bash
# Start ngrok
ngrok http 3000

# Use the ngrok URL as your webhook endpoint
# https://abc123.ngrok.io/webhooks/sesamy

Troubleshooting

Webhooks Not Received

  • Verify your endpoint is publicly accessible
  • Check that your server responds with a 2xx status code
  • Ensure your firewall allows incoming connections
  • Review webhook logs in the Sesamy dashboard (if available)

Duplicate Webhooks

  • Implement idempotency checks using event identifiers
  • Store processed event IDs to prevent reprocessing

Missing Data

  • Always fetch complete data from the API using the provided identifiers
  • Don't rely solely on webhook payload for critical data

Next Steps

Released under the MIT License.