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:
| Field | Type | Description |
|---|---|---|
url | string | Your endpoint URL |
headers | object | (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:
{
"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
| Field | Type | Description |
|---|---|---|
eventType | string | The type of event that occurred (see Event Types below) |
vendorId | string | The vendor identifier |
userId | string | The user identifier |
occured_at | string | ISO 8601 timestamp of when the event occurred |
contractId | string | (Optional) Contract identifier, if applicable |
entitlementId | string | (Optional) Entitlement identifier, if applicable |
checkoutId | string | (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 Type | Description |
|---|---|
userVendor.1#new | A new user-vendor relationship was created |
userVendor.1#update | A user-vendor relationship was updated |
contract.1#purchased | A contract was purchased |
contract.1#update | A contract was updated |
contract#cancelRequest | A cancellation request was made for a contract |
entitlement.1#new | A new entitlement was created for a user |
entitlement.1#update | An existing entitlement was updated |
checkout.1#confirm | A checkout was confirmed |
Note: entitlements created as part of a bundle (
origin: BUNDLE) do not raiseentitlement.1#neworentitlement.1#updateevents. They are filtered out before reaching any integration.
Setting Up Webhook Endpoints
Your webhook endpoint should:
- Accept POST requests
- Respond quickly (within a few seconds)
- Return a 2xx status code to acknowledge receipt
- Process the webhook asynchronously if needed
Example Endpoint
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:
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:
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.
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:
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:
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:
- Use a tool like ngrok to expose your local endpoint
- Ask your Sesamy account manager to point your test vendor's webhook endpoint at the ngrok URL (see Built-in Webhook Forwarding)
- Trigger events in your test environment
- Monitor the webhook delivery in your local application
# Start ngrok
ngrok http 3000
# Use the ngrok URL as your webhook endpoint
# https://abc123.ngrok.io/webhooks/sesamyTroubleshooting
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
- Integration Concepts -- shared concepts for built-in integrations
- API Reference -- API endpoints for fetching event data
- Authentication -- securing API requests
- Subscriptions Guide -- managing subscription contracts