Webhooks Overview
Webhooks deliver real-time HTTP POST notifications to your server when events occur on your orders. This eliminates the need to poll the API for status updates.
Supported Events
| Event | Emitted when |
|---|---|
order.completed | Payment completes successfully at order creation time |
order.fulfilled | All products are issued and their documents are ready to download |
order.failed | An order transitions to REJECTED or FAILED |
order.refunded | An order transitions to REFUND_COMPLETED |
order.cancelled | An unpaid order transitions to EXPIRED |
test | You call POST /webhooks/:id/test |
* | Wildcard subscription; receive all of the above |
order.completed and order.fulfilled are two different milestones. order.completed means the payment was accepted; nothing is issued yet. order.fulfilled means every line item is issued and its documents are stored, so it is the event to act on when you serve your own customers (for example when Vignetim customer emails are suppressed for your organization).
Asynchronous events (order.fulfilled, order.failed, order.refunded, order.cancelled) are delivered from the order pipeline and are deduplicated: at most one delivery per order and event type within 24 hours, even for multi-product orders. Sandbox orders only ever produce order.completed (with sandbox: true) and test events.
Webhook Envelope
Every delivery has the same envelope:
{
"event": "order.completed",
"data": { "...": "event-specific payload" },
"timestamp": "2026-03-20T14:31:15.000Z",
"webhookId": "1f2e3d4c-5b6a-7980-cdef-0123456789ab"
}Event Payloads
order.completed
Emitted at payment time. For live orders:
{
"event": "order.completed",
"data": {
"transactionId": "b6f0e2d4-8c1a-4e5f-9b3d-7a2c4e6f8a0b",
"status": "COMPLETED",
"externalReference": "YOUR-ORDER-REF-001"
},
"timestamp": "2026-03-20T14:31:15.000Z",
"webhookId": "1f2e3d4c-5b6a-7980-cdef-0123456789ab"
}Sandbox orders additionally carry orderId and sandbox: true:
{
"event": "order.completed",
"data": {
"orderId": "0d9c1c7e-3f4b-4d2a-9b1e-2f6a8c5d7e90",
"transactionId": "sandbox_a1b2c3d4e5f67890",
"status": "COMPLETED",
"sandbox": true,
"externalReference": "YOUR-ORDER-REF-001"
},
"timestamp": "2026-03-20T14:31:15.000Z",
"webhookId": "1f2e3d4c-5b6a-7980-cdef-0123456789ab"
}
data.orderIdis currently present on sandboxorder.completedevents only. On live events, correlate byexternalReference(recommended) ortransactionId.
order.fulfilled
Emitted once every line item of the order is COMPLETED and its documents are stored:
{
"event": "order.fulfilled",
"data": {
"orderId": "0d9c1c7e-3f4b-4d2a-9b1e-2f6a8c5d7e90",
"status": 4,
"statusLabel": "COMPLETED",
"externalReference": "YOUR-ORDER-REF-001",
"products": [
{
"productId": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"productTypeId": 1,
"status": 4,
"statusLabel": "COMPLETED"
}
],
"documents": [
{
"type": "CONFIRMATION_PDF",
"typeId": 1,
"orderProductId": "7e6d5c4b-3a2f-1e0d-9c8b-7a6f5e4d3c2b"
}
],
"documentsUrl": "https://api.vignetim.com/v2/partners/orders/0d9c1c7e-3f4b-4d2a-9b1e-2f6a8c5d7e90/documents",
"timestamp": "2026-03-21T09:12:00.000Z"
},
"timestamp": "2026-03-21T09:12:00.000Z",
"webhookId": "1f2e3d4c-5b6a-7980-cdef-0123456789ab"
}documents carries identifiers only. Download URLs are short-lived, so fetch them on demand from Get Order Documents (documentsUrl) when you are ready to deliver the files to your customer.
order.failed, order.refunded, order.cancelled
Delivered from the async status bridge:
{
"event": "order.failed",
"data": {
"orderId": "0d9c1c7e-3f4b-4d2a-9b1e-2f6a8c5d7e90",
"status": "REJECTED",
"externalReference": "YOUR-ORDER-REF-001",
"timestamp": "2026-03-21T09:12:00.000Z"
},
"timestamp": "2026-03-21T09:12:00.000Z",
"webhookId": "1f2e3d4c-5b6a-7980-cdef-0123456789ab"
}data.status is the raw order status name that triggered the event (REJECTED or FAILED for order.failed, REFUND_COMPLETED for order.refunded, EXPIRED for order.cancelled). externalReference is omitted if the order was created without one.
test
{
"event": "test",
"data": {
"message": "This is a test webhook delivery from Vignetim"
},
"timestamp": "2026-03-20T14:31:15.000Z",
"webhookId": "1f2e3d4c-5b6a-7980-cdef-0123456789ab"
}Delivery Headers
Each webhook delivery includes the following headers:
| Header | Description |
|---|---|
X-Webhook-Signature | HMAC-SHA256 hex signature of the request body, signed with the webhook's signingSecret |
X-Webhook-Event | The event type (e.g., order.completed) |
X-Webhook-Timestamp | ISO 8601 timestamp of when the event was generated |
Content-Type | application/json |
Signature Verification
Always verify the webhook signature to ensure the payload is authentic and has not been tampered with.
import crypto from 'crypto';
function verifyWebhookSignature(body, signature, secret) {
const expected = crypto.createHmac('sha256', secret).update(body).digest('hex');
return crypto.timingSafeEqual(Buffer.from(signature), Buffer.from(expected));
}
// In your webhook handler:
app.post('/webhooks/vignetim', (req, res) => {
const signature = req.headers['x-webhook-signature'];
const rawBody = req.rawBody; // Ensure you capture the raw body
if (!verifyWebhookSignature(rawBody, signature, 'your-webhook-signing-secret')) {
return res.status(401).send('Invalid signature');
}
const event = req.body;
console.log(`Received event: ${event.event}`);
// Process the event...
res.status(200).send('OK');
});The signingSecret is returned exactly once, in the POST /webhooks creation response.
Retry Policy
Each event is attempted up to 3 times, with a 10-second timeout per attempt:
| Attempt | Delay before attempt |
|---|---|
| 1st attempt | Immediate |
| 2nd attempt | 1 second |
| 3rd attempt | 5 seconds |
A delivery succeeds on any 2xx response. After all 3 attempts fail, the delivery is marked as failed and the webhook's failureCount is incremented; a successful delivery resets it to 0.
Auto-Disable
If a webhook endpoint accumulates 10 consecutive delivery failures, it is automatically disabled (active: false). Re-enable it with PUT /webhooks/:id and { "active": true } once your endpoint is healthy. Use POST /webhooks/:id/test to verify connectivity first.