Skip to main content
When events fire in rapid succession, such as a user saving a document repeatedly, you often only want to perform a reaction (like syncing to an external API) once the activity stops. Instead of cancelling previous hooks (which requires tracking IDs), you can use a Stale Check pattern.

Workflow

  1. Schedule: On every event, schedule a hook for X minutes in the future. Pass the current timestamp.
  2. Verify: When the hook fires, compare the passed timestamp with the resource’s current updatedAt.
  3. Discard: If the resource is newer than the event, it means a subsequent event occurred. Discard this hook.

Example: Sync to Salesforce

Sync a user profile to Salesforce 5 minutes after their last edit.
import Posthook from '@posthook/node';
import { Signatures, SignatureVerificationError } from '@posthook/node';

const posthook = new Posthook(process.env.POSTHOOK_API_KEY);
const signatures = new Signatures(process.env.POSTHOOK_SIGNING_KEY);

/* 1. Schedule */
async function onUserProfileUpdate(user) {
  // Always schedule the sync for 5 minutes later
  // Pass the creation time of THIS specific event
  await posthook.hooks.schedule({
    path: '/webhooks/sync/salesforce',
    postIn: '5m',
    data: {
      userId: user.id,
      triggeredAt: new Date().toISOString()
    }
  });
}

/* 2. Handle */
app.post('/webhooks/sync/salesforce', async (req, res) => {
  try {
    const delivery = signatures.parseDelivery(req.body, req.headers);

    const { userId, triggeredAt } = delivery.data;
    const user = await db.getUser(userId);

    // STALE CHECK:
    // If the user record has been updated SINCE this hook was scheduled,
    // then a newer hook exists in the queue. We can safely skip this one.
    if (new Date(user.updatedAt) > new Date(triggeredAt)) {
      return res.status(200).send('Stale request, skipping');
    }

    // If timestamps match (or are very close), this is the latest action.
    await salesforce.sync(user);
    res.status(200).send('Synced');
  } catch (err) {
    if (err instanceof SignatureVerificationError) {
      return res.status(401).json({ error: err.message });
    }
    throw err;
  }
});
This ensures that if a user updates their profile 10 times in one minute, only the last hook will actually trigger the sync. The previous 9 will detect they are “stale” and exit immediately.