Use webhooks to react within seconds, and keep polling as a safety net. A webhook tells you that a record changed; your connector then fetches the current record from the API and runs the same handler the poller uses.
Register a webhook
Register through the API with your Integration API Key (or in the integration's Advanced section in the app). Choose a long random value for apikey: Pipe17 sends it back in the x-api-key header of every delivery, so your endpoint can reject calls that do not carry it.
curl -s -X POST "$PIPE17_BASE_URL/webhooks" \
-H "X-Pipe17-Key: $PIPE17_INTEGRATION_KEY" \
-H "Content-Type: application/json" \
-d '{
"url": "https://connector.example.com/pipe17/webhooks",
"apikey": "<long random secret>",
"topics": ["shipments", "products", "integrations"]
}'The URL must be unique within the organization. Available topics include shipments, orders, fulfillments, inventory, products, locations, integrations, returns, arrivals, receipts and exceptions. Use GET /webhooks, PUT /webhooks/{webhookId} and DELETE /webhooks/{webhookId} to review and maintain your subscriptions.
# List your webhooks
curl -s "$PIPE17_BASE_URL/webhooks" \
-H "X-Pipe17-Key: $PIPE17_INTEGRATION_KEY"
# Change the topics of one webhook
curl -s -X PUT "$PIPE17_BASE_URL/webhooks/<webhookId>" \
-H "X-Pipe17-Key: $PIPE17_INTEGRATION_KEY" \
-H "Content-Type: application/json" \
-d '{"topics": ["shipments", "products", "integrations", "locations"]}'
# Remove a webhook
curl -s -X DELETE "$PIPE17_BASE_URL/webhooks/<webhookId>" \
-H "X-Pipe17-Key: $PIPE17_INTEGRATION_KEY"What Pipe17 sends
When a record of a subscribed topic is created, updated or deleted, Pipe17 sends a POST to your URL with an x-api-key header and a small JSON body. The body names the record; it does not contain it.
{
"orgKey": "a5ad27e3a383b711",
"entity": "shipments",
"action": "update",
"itemId": "930cfcd3018dffd5",
"timestamp": "2026-10-12T07:18:31Z",
"requestId": "b020289f41cc0aee"
}| Field | Meaning |
|---|---|
entity |
The topic, for example shipments or products
|
action |
create, update or delete
|
itemId |
The Pipe17 ID of the record that changed. Fetch it from the API to get its current data |
timestamp |
When the change happened |
requestId |
Unique per event. Use it to ignore repeat deliveries |
orgKey |
The Pipe17 organization |
Delivery rules. If Pipe17 cannot reach your endpoint, it retries 3 times over about 5 minutes and then drops the event. Any HTTP response, including 4xx and 5xx, counts as delivered and is not retried. A failure inside your receiver therefore loses the event, which is one reason polling must stay on.
Receive a webhook
Your endpoint should do as little as possible: check the call is genuine, record it, reply 200, and process it afterwards.
// webhooks.js – receiver (Node.js built-in http server; Express works the same way)
import http from 'node:http';
import { handleShippingRequest } from './flows/shippingRequests.js';
import { handleProduct } from './flows/products.js';
import { refreshSettings } from './settings.js';
import { p17 } from './p17.js';
import * as state from './state.js';
const SECRET = process.env.PIPE17_WEBHOOK_SECRET; // the apikey you registered
const fetchers = {
shipments: async (id) => (await p17('GET', `/shipping_requests/${id}`, { query: { keys: '*' } })).shipment,
products: async (id) => (await p17('GET', `/products/${id}`)).product,
};
const handlers = { shipments: handleShippingRequest, products: handleProduct };
http.createServer((req, res) => {
if (req.method !== 'POST' || req.url !== '/pipe17/webhooks') return res.writeHead(404).end();
if (req.headers['x-api-key'] !== SECRET) return res.writeHead(401).end();
let raw = '';
req.on('data', (chunk) => (raw += chunk));
req.on('end', () => {
res.writeHead(200).end(); // acknowledge first, work later
setImmediate(() => processDelivery(JSON.parse(raw)).catch(console.error));
});
}).listen(process.env.PORT ?? 8080);
async function processDelivery({ orgKey, entity, action, itemId, requestId }) {
const ctx = await contextFor(orgKey); // your registry: integration, key and settings for this org
if (state.wasProcessed(`webhook:${requestId}`)) return; // repeat delivery
if (entity === 'integrations') await refreshSettings();
else if (handlers[entity] && action !== 'delete') {
const record = await fetchers[entity](itemId); // always re-read the current record
await handlers[entity](ctx, record); // same idempotent handler the poller uses
}
state.markProcessed(`webhook:${requestId}`);
}In production, write each delivery to a durable queue or table before replying, rather than setImmediate. Because Pipe17 does not retry once you respond, anything lost after the reply is only picked up by the next poll.
Two keys, two jobs. The x-api-key value proves a delivery came from your webhook subscription. Fetching the record still uses your Integration API Key in the X-Pipe17-Key header.
Fetching the record named in a delivery (itemId):
# entity = shipments curl -s -G "$PIPE17_BASE_URL/shipping_requests/<itemId>" \ -H "X-Pipe17-Key: $PIPE17_INTEGRATION_KEY" \ --data-urlencode "keys=*" # entity = products curl -s "$PIPE17_BASE_URL/products/<itemId>" \ -H "X-Pipe17-Key: $PIPE17_INTEGRATION_KEY"
Combine webhooks with polling
| Webhooks | Polling | |
|---|---|---|
| Speed | Seconds | One interval (minutes) |
| Can miss changes | Yes: endpoint down, network errors, deploys | No, if cursors are handled as in Chapter 5 |
| Duplicates | Possible | Possible (overlap window) |
| Role in your connector | Fast path | Source of truth and catch-up |
With webhooks on, you can lengthen poll intervals (for example from 5 to 15 minutes) but never turn polling off. Both paths call the same handler, and the event keys from Chapter 4 make repeats harmless.
Need Help?
If you need additional assistance:
- Use Ask Pippen, our AI agent, located at the top of the app page.
- Submit a support request with as much relevant detail as possible. Learn how to submit a request.
We're here to help you succeed with your operations.
Comments
0 comments