Build with Pinport

Webhooks

Webhooks allow you to receive real-time notifications when events occur in your Pinport account. You can configure webhooks to send data to your application or service whenever a bookmark is created, updated, deleted, or when bulk operations occur.

Creating Webhooks

You can create webhooks in your integration settings. Each webhook requires:

  • URL - The endpoint that will receive webhook payloads
  • Event type - The specific event you want to listen for
  • Description (optional) - A note to help you identify the webhook's purpose

You can also generate a signing secret when creating a webhook. This allows you to verify that webhook requests are coming from Pinport.

Event Types

The following event types are available for webhooks:

Single Bookmark Events

  • bookmark.created - Triggered when a bookmark is created
  • bookmark.updated - Triggered when a bookmark is updated
  • bookmark.deleted - Triggered when a bookmark is deleted

Bulk Operation Events

  • bookmarks.imported - Triggered when bookmarks are imported in bulk
  • bookmarks.deleted - Triggered when multiple bookmarks are deleted
  • bookmarks.updated - Triggered when multiple bookmarks are updated
  • tag.renamed - Triggered when a tag is renamed across multiple bookmarks
  • tag.deleted - Triggered when a tag is removed from multiple bookmarks

Webhook Payloads

All webhook payloads share a common structure. Bulk operations emit their bulk event instead of one event per bookmark. The specific data included depends on the event type. Here are examples of the different payload formats:

Bookmark objects in payloads have the same shape as in API v2.

Common Structure

{
    "id": "webhook-delivery-id",
    "event": "event.type",
    "timestamp": "2023-06-15T12:30:45Z",
    "data": {
        // Event-specific data
    }
}

bookmark.created

{
    "id": "webhook-delivery-id",
    "event": "bookmark.created",
    "timestamp": "2023-06-15T12:30:45Z",
    "data": {
        "id": "abc123",
        "url": "https://example.com",
        "title": "Example Website",
        "note": "Why I saved this",
        "tags": ["example", "demo"],
        "createdAt": "2023-06-15T12:30:45Z",
        "updatedAt": "2023-06-15T12:30:45Z"
    }
}

bookmark.updated

{
    "id": "webhook-delivery-id",
    "event": "bookmark.updated",
    "timestamp": "2023-06-15T12:30:45Z",
    "data": {
        "old": {
            "id": "abc123",
            "url": "https://example.com",
            "title": "Old Title",
            "note": "Old note",
            "tags": ["old", "tags"],
            "createdAt": "2023-06-15T12:30:45Z",
            "updatedAt": "2023-06-15T12:30:45Z"
        },
        "new": {
            "id": "abc123",
            "url": "https://example.com",
            "title": "Updated Title",
            "note": "Updated note",
            "tags": ["updated", "tags"],
            "createdAt": "2023-06-15T12:30:45Z",
            "updatedAt": "2023-06-15T13:45:30Z"
        }
    }
}

bookmark.deleted

{
    "id": "webhook-delivery-id",
    "event": "bookmark.deleted",
    "timestamp": "2023-06-15T12:30:45Z",
    "data": {
        "id": "abc123",
        "url": "https://example.com",
        "title": "Example Website",
        "note": "Why I saved this",
        "tags": ["example", "demo"],
        "createdAt": "2023-06-15T12:30:45Z",
        "updatedAt": "2023-06-15T12:30:45Z"
    }
}

Bulk Operations

Bulk operation payloads include a count and list of affected bookmark IDs:

{
    "id": "webhook-delivery-id",
    "event": "bookmarks.imported",
    "timestamp": "2023-06-15T12:30:45Z",
    "data": {
        "count": 25,
        "bookmarkIds": ["abc123", "def456", "ghi789", "..."]
    }
}

Tag Operations

{
    "id": "webhook-delivery-id",
    "event": "tag.renamed",
    "timestamp": "2023-06-15T12:30:45Z",
    "data": {
        "count": 10,
        "bookmarkIds": ["abc123", "def456", "ghi789", "..."],
        "oldTag": "javascript",
        "newTag": "js"
    }
}

Security

When creating a webhook, you can generate a signing secret. This secret is used to create a signature that is sent with each webhook payload, allowing you to verify that the request came from Pinport.

The signature is included in the X-Webhook-Signature header of the webhook request. It is calculated using HMAC-SHA256 with your webhook secret.

Verifying Signatures

To verify webhook signatures:

  1. Get the signature from the X-Webhook-Signature header
  2. Create an HMAC-SHA256 hash of the request body using your webhook secret
  3. Compare the two signatures

Verify the raw request bytes, before parsing JSON. This Node.js example checks the signature format before comparing buffers of equal length:

// Node.js + Express. Register this route BEFORE express.json().
import express from 'express';
import { createHmac, timingSafeEqual } from 'node:crypto';
const app = express();
const secret = process.env.PINPORT_WEBHOOK_SECRET;
if (!secret) throw new Error('Configure PINPORT_WEBHOOK_SECRET');

app.post('/pinport-webhook', express.raw({ type: 'application/json' }), (req, res) => {
  const signature = req.get('X-Webhook-Signature') ?? '';
  if (!/^[a-f0-9]{64}$/i.test(signature)) return res.sendStatus(401);
  const expected = createHmac('sha256', secret).update(req.body).digest();
  const supplied = Buffer.from(signature, 'hex');
  if (!timingSafeEqual(supplied, expected)) return res.sendStatus(401);
  const event = JSON.parse(req.body.toString('utf8'));
  // Persist event.id and enqueue work before acknowledging.
  // Ignore event IDs you have already processed.
  res.sendStatus(204);
});

Best Practices

  • Respond quickly - Your endpoint should return a 2xx status code as quickly as possible. Process the webhook asynchronously if needed.
  • Handle failures - Make sure your webhook endpoint can handle temporary failures Pinport retries network failures and HTTP 408, 429, 500, 502, 503, and 504 responses up to four times with exponential backoff. Each attempt has a five-second timeout. Delivery is best effort, not a durable event log; rate limits or exhausted retries can leave gaps. Reconcile important data with the API.
  • Verify signatures - Always verify webhook signatures to ensure the requests are coming from Pinport.
  • Idempotency - While we take measures to prevent duplicate webhook deliveries, designing your webhook handlers to be idempotent is still recommended as a best practice for resilient integrations.