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 createdbookmark.updated- Triggered when a bookmark is updatedbookmark.deleted- Triggered when a bookmark is deleted
Bulk Operation Events
bookmarks.imported- Triggered when bookmarks are imported in bulkbookmarks.deleted- Triggered when multiple bookmarks are deletedbookmarks.updated- Triggered when multiple bookmarks are updatedtag.renamed- Triggered when a tag is renamed across multiple bookmarkstag.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:
- Get the signature from the
X-Webhook-Signatureheader - Create an HMAC-SHA256 hash of the request body using your webhook secret
- 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.