Create Order
Submit a new order with product selection, payment details, and customer information.
POST /ordersThis endpoint requires an HMAC signature (see Authentication). Request bodies are strictly validated: unknown fields are rejected with a 400.
Request Body
{
"products": [
{
"productId": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"productTypeId": 1,
"quantity": 1,
"vehiclePlate": "AB123CD",
"vehicleCountryIsocode": "AT",
"vehicleCategoryId": "f1e2d3c4-b5a6-7890-abcd-ef1234567890",
"startAt": "2026-04-01T00:00:00.000Z",
"endAt": "2026-04-10T23:59:59.000Z",
"driverFirstname": "John",
"driverLastname": "Doe"
}
],
"payment": {
"method": "saved_card",
"savedCardId": "3f2b1a0c-9d8e-7f6a-5b4c-3d2e1f0a9b8c",
"returnUrl": "https://yourapp.com/payment/callback"
},
"customer": {
"email": "john.doe@example.com",
"firstName": "John",
"lastName": "Doe",
"phone": "+43123456789"
},
"address": {
"countryIsocode": "AT",
"city": "Vienna",
"addressLine": "Mariahilfer Strasse 1",
"postalCode": "1060",
"taxNumber": "ATU12345678",
"companyName": "Doe GmbH"
},
"idempotencyKey": "unique-order-key-12345",
"externalReference": "YOUR-ORDER-REF-001",
"callbackUrl": "https://yourapp.com/webhooks/vignetim"
}Products Array
1 to 50 items per order.
| Field | Type | Required | Description |
|---|---|---|---|
productId | string | Yes | Ticket UUID, eSIM package ID, or insurance product slug (max 255 chars) |
productTypeId | integer | Yes | Product type: 1 = TICKET (vignette), 2 = ESIM, 4 = INSURANCE, 5 = FINES |
quantity | integer | Yes | Number of units (min 1) |
vehiclePlate | string | Conditional | Vehicle license plate, max 20 chars (vignettes) |
vehicleCountryIsocode | string | Conditional | Vehicle registration country ISO code, max 5 chars (vignettes) |
vehicleCategoryId | string | Conditional | Vehicle category ID, max 100 chars (vignettes) |
startAt | ISO 8601 | Conditional | Validity start date |
endAt | ISO 8601 | Conditional | Validity end date |
driverFirstname | string | No | Driver first name, max 100 chars (required by some countries) |
driverLastname | string | No | Driver last name, max 100 chars (required by some countries) |
productTypeId value 3 (SERVICE) is internal and not orderable through the Partner API.
Payment Object
| Field | Type | Required | Description |
|---|---|---|---|
method | string | Yes | saved_card or wallet |
savedCardId | UUID | Conditional | Required when method is saved_card. Must be a card vaulted for your organization; unknown or foreign card IDs return 404 |
returnUrl | string | No | HTTPS URL for redirect-based flows |
No raw card data is ever accepted on this API.
Payment Routing
- saved_card -- Charges a card previously vaulted through the partner dashboard card registration flow (a one-time 3D Secure verification). The charge is processed by NestPay. See Saved Cards.
- wallet -- Debits your prepaid EUR organization wallet. The wallet is funded by bank transfer, posted by the Vignetim back office. If the balance does not cover the order, the API returns a
422with codeINSUFFICIENT_WALLET_BALANCE(see Wallet).
Customer Object
| Field | Type | Required | Description |
|---|---|---|---|
email | string | Yes | Customer email address |
firstName | string | Yes | Customer first name, max 100 chars |
lastName | string | Yes | Customer last name, max 100 chars |
phone | string | No | Customer phone number, max 20 chars (E.164 format recommended) |
Instead of the inline customer object you can send a top-level customerLinkId (UUID of a stored customer link) and the customer details are filled from the stored record. Provide exactly one of customer or customerLinkId; sending both or neither returns a 400.
Address Object
| Field | Type | Required | Description |
|---|---|---|---|
countryIsocode | string | Yes | Country ISO code, max 5 chars |
city | string | Yes | City name, max 100 chars |
addressLine | string | Yes | Street address, max 300 chars |
postalCode | string | No | Postal/ZIP code, max 20 chars |
taxNumber | string | No | Tax/VAT number, max 50 chars (B2B invoicing) |
companyName | string | No | Company name, max 200 chars (B2B invoicing) |
Additional Fields
| Field | Type | Required | Description |
|---|---|---|---|
customerLinkId | UUID | No | Stored customer link ID; replaces the inline customer object (mutually exclusive) |
idempotencyKey | string | No | Unique key to prevent duplicate orders, max 255 chars (strongly recommended) |
externalReference | string | No | Your internal order reference for tracking, max 255 chars |
callbackUrl | string | No | HTTPS URL for payment callbacks. Falls back to payment.returnUrl when omitted |
All URLs must be HTTPS.
Example Request
With method: 'wallet', drop savedCardId from the payment object.
API_KEY="your-api-key"
API_SECRET="your-api-secret"
# Signing key = SHA-256 hex digest of your API secret
# $NF, not $2: OpenSSL prints "SHA2-256(stdin)= <hash>" but LibreSSL (the macOS
# default) prints the bare hash, where $2 is empty β that yields an empty signing
# key and a 401 with nothing to indicate why.
SIGNING_KEY=$(echo -n "${API_SECRET}" | openssl dgst -sha256 | awk '{print $NF}')
TIMESTAMP=$(date +%s)
NONCE=$(uuidgen | tr '[:upper:]' '[:lower:]')
METHOD="POST"
REQ_PATH="/v2/partners/orders"
# No request body: sign an empty body string.
BODY=""
MESSAGE="${TIMESTAMP}.${NONCE}.${METHOD}.${REQ_PATH}.${BODY}"
SIGNATURE=$(echo -n "${MESSAGE}" | \
openssl dgst -sha256 -hmac "${SIGNING_KEY}" | \
awk '{print $NF}')
curl -X POST "https://api.vignetim.com${REQ_PATH}" \
-H "X-API-Key: ${API_KEY}" \
-H "X-Timestamp: ${TIMESTAMP}" \
-H "X-Nonce: ${NONCE}" \
-H "X-Signature: ${SIGNATURE}"Response
201 Created
The same shape is returned for live orders, sandbox orders, and idempotency replays:
{
"transactionId": "b6f0e2d4-8c1a-4e5f-9b3d-7a2c4e6f8a0b",
"status": "COMPLETED",
"statusLabel": "COMPLETED",
"redirectUrl": "https://pay.vignetim.com/3ds/verify/abc123"
}| Field | Type | Description |
|---|---|---|
transactionId | string | Payment transaction ID (bare UUID; sandbox_<16hex> in sandbox) |
status | string | Payment transaction status, e.g. COMPLETED, FAILED, PENDING_3D |
statusLabel | string | Same as status; human-readable payment status |
orderId | string | Created order ID. Currently returned for sandbox orders only; for live orders, list by externalReference or wait for the webhook |
redirectUrl | string | Present for redirect-based flows; send the customer here to complete the payment |
sandbox | boolean | true when the order was created with a TEST key |
Error Responses
| Status | Cause |
|---|---|
400 | Validation failure, unknown fields, missing savedCardId for saved_card, or daily order limit exceeded |
403 | Missing/invalid signature, or your organization is not authorized to place orders |
404 | savedCardId not found or not owned by your organization |
409 | A request with this idempotency key is already in flight |
422 | Insufficient wallet balance (INSUFFICIENT_WALLET_BALANCE) |
429 | Order creation rate limit (10/min) exceeded |
Idempotency
Order creation is idempotent per (organization, idempotencyKey):
- The first request with a key claims it atomically before any work starts.
- A concurrent request with the same key while the first is still processing returns
409withA request with this idempotency key is already in flight. - Once the first request completes, its result is cached and replayed for 24 hours: retries with the same key return the original response without creating a new order or charging again.
- If a request fails before payment was initiated (validation error, missing card, insufficient balance, daily limit), the claim is released immediately and the key can be retried right away.
- If a request fails after payment was initiated, the claim is held for up to about 5 minutes (duplicates get
409in that window) to protect against double charges; after that, retries with the same key are deduplicated by the payment layer.
Always generate one idempotency key per logical order and reuse it for all retries of that order.