# Get data Source: https://developer.priceedge.com/api-reference/endpoint/GET-DATA This endpoint takes a table name as parameter and fitlers in the body ## Get data ```Http theme={null} POST {api_url}/api/tables/{TableName} ``` ```Http theme={null} POST {api_url}/api/tables/{TableName} HTTP/1.1 Content-Type: application/json Authorization: Bearer {token} // or APIKey client_id: {unique client id} // when API key client id not needed { "page": 1, "nrOfRecords": 100, "filters": [ { "columnName": "Material", "op":"=", "value": "Steel" } ], "fields": ["cd_ItemNumber", "Material", "Width", "Supplier"], "orderby": ["cd_ItemNumber"] } ``` The response for a successful request would be ```JSON theme={null} { "Data": { "data": [ { "cd_ItemNumber": "ABC123", "Material": "Steel", "Width": 10, "Supplier": "" } ], "totalNumberOfRecords": 1 }, "Errors": [], "Succeeded": true } ``` ``` <,>, !=, >=, <=, ContainsAny (when using ContainsAny, "value": "Steel,Copper,Alu", is a comma separated list) ``` You can add several filter criteria in the same request. The exmaple below would give you all that has Cost greater than 100 and smaller than 200 ```JSON theme={null} "filters": [ { "columnName": "Cost", "op":">", "value": "100.0" }, { "columnName": "Cost", "op":"<", "value": "200.0" } ] ``` # Set data Source: https://developer.priceedge.com/api-reference/endpoint/SET-DATA This takes a table namn as parameter and updates or insert the data. ### SET DATA Use this action to add or update values in tables. Limit to 2000 objects per request. In the example below we would insert and/or update two item\_extension objects. Use the request method **PUT** ```Http theme={null} PUT {api_url}/api/tables/{TableName} HTTP/1.1 Content-Type: application/json Authorization: Bearer {token} //or APIKey client_id: {unique client id} //when API key client id not needed [ { "cd_ItemNumber": "ABC123", "Material": "Steel", "Width": 10, "Supplier": "Supplier Name" }, { "cd_ItemNumber": "ABC125", "Material": "Alu", "Width": 20, "Supplier": "Supplier Name" } ] ``` # Authentication Source: https://developer.priceedge.com/api-reference/introduction There are two ways of authentication: Bearer token and ApiKey ### API url The`{api_url}` is **your company specific url** + priceedge.eu/papi. If your company url is **yourdomain** then the API-url would be `yourdomain.priceedge.eu/papi` In examples the API-url is **yourdomain.priceedge.eu/papi/** ### Username and password From the PriceEdge team or your system owner you should have received a username. The password, if not already set, you set via the link in mail you should have received. These are used for ApiKey auth and to get bearer token. ### Bearer token Authentication is done using a Bearer Token, particular type of access token, with the property that anyone can use the token. In other words, a client doesn’t need a cryptographic key or other secret to use a bearer token. For this reason, the PriceEdge API works only on HTTPS. The token is generated with a call to an endpoint with different headers and with a username and password. Beside the generated token, which has a limited expiration time - which is configurable for each client - there will be also created a Refresh Token which is a special kind of token that can be used to obtain a renewed Bearer Token at any time. Refresh tokens must be stored securely by an application because they essentially allow a user to remain authenticated for a long time. Requests made with invalid credentials value will receive an HTTP 403 – Forbidden response. On the next pages we will explain in more depth how the security endpoint can be used to obtain the Bearer Token using the username/password or the refresh token ### Obtain Bearer token Application has no security token, needs to make a secure call with the user and password to the **PriceEdge API** to get a valid token. Make a POST request to the API: ```http HTTP Example theme={null} POST /{api_url}/token Content-Type: application/x-www-form-urlencoded grant_type=password&username={username}&password={user password}& client_id={unique client id} ``` The result will have the following format: ``` { "access_token": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1bmlxdWVfbmFtZSI6InNwYXJrd2FyZVxccmF6dm.......9sZSI6WyJ1c2VyIiwiYWRtaW4iXSwic3ViIjoic3Bhcmt3YXJlXFxyYXp2YW5wYWkiLCJpc3MiOiJsb2NhbGhvc3QiLCJhdWQiOiJjE1ZTA1ZTg4YTc0ZmQ3OGQ1ZGUwM2U5NGRhMDI0NSIsImV4cCI6MTQ2OTAwODI0MCwibmJmIjoxNDY5MDA4MTgwfQ.Ps8NuXgMvPP3nXdz0usLVEvmiZMbp84x_d9Rwhlb2kc", "token_type": "bearer", "expires_in": 59, "refresh_token": "5a8a3c59c0844cda947e71b3d613d526", "as:client_id": "clientId", "userName": "someUserName", ".issued": "Wed, 20 Jul 2016 09:49:40 GMT", ".expires": "Wed, 20 Jul 2016 09:50:40 GMT" } ``` Notes: **access\_token** will be stored on the client app and used in every request made to the api. The access token is set to expire in 20 minutes. After that a new refresh token has to be issued in 2 ways:\ **First option**: the one explained above, by using a post call to the token endpoint with the user name and password. **Second option**: send the refresh token to the obtain token endpoint ```http HTTP Example theme={null} POST /{api_url}/token HTTPS/1.1 Content-Type: application/x-www-form-urlencoded grant_type=refresh_token& refresh_token=5a8a3c59c0844cda947e71b3d613d526& client_id={unique client id} ``` **refresh\_token** will be stored on the client app and used to obtain a new **access\_token** when that will expire. This will expire after a longer period than the access\_token (suggested are 5 days) In this example the username is [pricing@example.com](mailto:pricing@example.com) and \*\**MYPASSWORD123* as the password ### First request - GET item metadata Bearer Token ```http HTTP Example theme={null} GET /{api_url}/api/tables/item Content-Type: application/json Authorization: Bearer {token} client_id: {unique client id} ``` ### First request - GET item metadata ApiKey Don' forget to change to you domain, username and password ```http HTTP Example theme={null} HTTP /{api_url}/api/tables/item Authorization: ApiKey pricing@example.com:MYPASSWORD123 Content-Type: application/json ``` ```bash cURL Example theme={null} curl 'https://yourdomain.priceedge.eu/papi/api/tables/item' \ --header 'Authorization: ApiKey pricing@example.com:MYPASSWORD123' \ --header 'Content-Type: application/json' ``` ApiKey is case sensitive and note the : between username and password ## Expected Response The return body will contain a lot of information but for now we are happy that you can connect. Let's move on to next step. The returned data. ```JSON theme={null} { "Data": [ { "column_id": 12, "name": "_dtLastModifiedDate", "typeName": "datetime2", "max_length": 8, "precision": 27, "scale": 7, "is_nullable": true, "default_value": "(sysdatetime())", "is_unique": null, "is_primary_key": false, "enableInABC": null, "typeName_display": "datetime2", "precision_display": null, "table_name": "Item", "DATA_TYPE": "Text", "FORMATTING": "", "ALIGN": "Left", "ViewSecurityRightID": null, "EditSecurityRightID": null, "EditTableSecurityRightID": "dataadmin-item_edit", "canEdit": true, "IsKey": false, "Condition": "and Number = '@Data.Number~'", "isPriceColumn": null }, { "column_id": 6, "name": "cd_PriceGroup", "typeName": "varchar", "max_length": 20, "precision": 0, "scale": 0, "is_nullable": true, "default_value": null, "is_unique": null, "is_primary_key": false, "enableInABC": null, "typeName_display": "fk(PriceGroup.Code)", "precision_display": "20", "table_name": "Item", "DATA_TYPE": "Text", "FORMATTING": "", "ALIGN": "Left", "ViewSecurityRightID": null, "EditSecurityRightID": null, "EditTableSecurityRightID": "dataadmin-item_edit", "canEdit": true, "IsKey": false, "Condition": "and Number = '@Data.Number~'", "isPriceColumn": null }, { "column_id": 4, "name": "CostForPricing", "typeName": "decimal", "max_length": 9, "precision": 18, "scale": 4, "is_nullable": true, "default_value": null, "is_unique": null, "is_primary_key": false, "enableInABC": null, "typeName_display": "decimal(18,4)", "precision_display": "18,4", "table_name": "Item", "DATA_TYPE": "Number", "FORMATTING": "###,###,##0.00", "ALIGN": "Right", "ViewSecurityRightID": "dataadmin-item_CostForPricing_view", "EditSecurityRightID": "dataadmin-item_CostForPricing_edit", "EditTableSecurityRightID": "dataadmin-item_edit", "canEdit": true, "IsKey": false, "Condition": "and Number = '@Data.Number~'", "isPriceColumn": null }, { "column_id": 5, "name": "Description", "typeName": "nvarchar", "max_length": 0, "precision": 0, "scale": 0, "is_nullable": true, "default_value": null, "is_unique": null, "is_primary_key": false, "enableInABC": null, "typeName_display": "nvarchar(0)", "precision_display": "0", "table_name": "Item", "DATA_TYPE": "Text", "FORMATTING": "", "ALIGN": "Left", "ViewSecurityRightID": null, "EditSecurityRightID": null, "EditTableSecurityRightID": "dataadmin-item_edit", "canEdit": true, "IsKey": false, "Condition": "and Number = '@Data.Number~'", "isPriceColumn": null }, { "column_id": 10, "name": "ImageUrl", "typeName": "nvarchar", "max_length": 1000, "precision": 0, "scale": 0, "is_nullable": true, "default_value": null, "is_unique": null, "is_primary_key": false, "enableInABC": null, "typeName_display": "nvarchar(1000)", "precision_display": "1000", "table_name": "Item", "DATA_TYPE": "Text", "FORMATTING": "", "ALIGN": "Left", "ViewSecurityRightID": null, "EditSecurityRightID": null, "EditTableSecurityRightID": "dataadmin-item_edit", "canEdit": true, "IsKey": false, "Condition": "and Number = '@Data.Number~'", "isPriceColumn": null }, { "column_id": 8, "name": "InsertDate", "typeName": "datetime", "max_length": 8, "precision": 23, "scale": 3, "is_nullable": false, "default_value": "(getdate())", "is_unique": null, "is_primary_key": false, "enableInABC": null, "typeName_display": "datetime", "precision_display": null, "table_name": "Item", "DATA_TYPE": "Date", "FORMATTING": "Short Date", "ALIGN": "Right", "ViewSecurityRightID": null, "EditSecurityRightID": null, "EditTableSecurityRightID": "dataadmin-item_edit", "canEdit": true, "IsKey": false, "Condition": "and Number = '@Data.Number~'", "isPriceColumn": null }, { "column_id": 2, "name": "Name", "typeName": "nvarchar", "max_length": 1000, "precision": 0, "scale": 0, "is_nullable": true, "default_value": null, "is_unique": null, "is_primary_key": false, "enableInABC": null, "typeName_display": "nvarchar(1000)", "precision_display": "1000", "table_name": "Item", "DATA_TYPE": "Text", "FORMATTING": "", "ALIGN": "Left", "ViewSecurityRightID": null, "EditSecurityRightID": null, "EditTableSecurityRightID": "dataadmin-item_edit", "canEdit": true, "IsKey": false, "Condition": "and Number = '@Data.Number~'", "isPriceColumn": null }, { "column_id": 9, "name": "NeedsPrice", "typeName": "bit", "max_length": 1, "precision": 1, "scale": 0, "is_nullable": false, "default_value": "(isnull([dbo].[usfGetSystemSetting]('DefaultNeedsPrice'),(0)))", "is_unique": null, "is_primary_key": false, "enableInABC": null, "typeName_display": "bit", "precision_display": null, "table_name": "Item", "DATA_TYPE": "Number", "FORMATTING": "True/False", "ALIGN": "Right", "ViewSecurityRightID": null, "EditSecurityRightID": null, "EditTableSecurityRightID": "dataadmin-item_edit", "canEdit": true, "IsKey": false, "Condition": "and Number = '@Data.Number~'", "isPriceColumn": null }, { "column_id": 1, "name": "Number", "typeName": "nvarchar", "max_length": 128, "precision": 0, "scale": 0, "is_nullable": false, "default_value": null, "is_unique": true, "is_primary_key": true, "enableInABC": null, "typeName_display": "nvarchar(128)", "precision_display": "128", "table_name": "Item", "DATA_TYPE": "Text", "FORMATTING": "", "ALIGN": "Left", "ViewSecurityRightID": null, "EditSecurityRightID": null, "EditTableSecurityRightID": "dataadmin-item_edit", "canEdit": false, "IsKey": true, "Condition": "and Number = '@Data.Number~'", "isPriceColumn": null }, { "column_id": 3, "name": "SearchName", "typeName": "nvarchar", "max_length": 1024, "precision": 0, "scale": 0, "is_nullable": true, "default_value": null, "is_unique": null, "is_primary_key": false, "enableInABC": null, "typeName_display": "nvarchar(1024)", "precision_display": "1024", "table_name": "Item", "DATA_TYPE": "Text", "FORMATTING": "", "ALIGN": "Left", "ViewSecurityRightID": null, "EditSecurityRightID": null, "EditTableSecurityRightID": "dataadmin-item_edit", "canEdit": true, "IsKey": false, "Condition": "and Number = '@Data.Number~'", "isPriceColumn": null }, { "column_id": 11, "name": "Url", "typeName": "nvarchar", "max_length": 1000, "precision": 0, "scale": 0, "is_nullable": true, "default_value": null, "is_unique": null, "is_primary_key": false, "enableInABC": null, "typeName_display": "nvarchar(1000)", "precision_display": "1000", "table_name": "Item", "DATA_TYPE": "Text", "FORMATTING": "", "ALIGN": "Left", "ViewSecurityRightID": null, "EditSecurityRightID": null, "EditTableSecurityRightID": "dataadmin-item_edit", "canEdit": true, "IsKey": false, "Condition": "and Number = '@Data.Number~'", "isPriceColumn": null } ], "Errors": [], "Succeeded": true } ``` # Extract prices Source: https://developer.priceedge.com/api-reference/price-extraction/extract-prices Read records from a price column's source table with filtering, sorting, pagination, and optional cache-backed snapshots. ## Extract prices Reads records from the source table of the given price column. Supports filtering, field selection, sorting, and pagination. Optionally builds a **cache snapshot** to guarantee consistent ordering and pagination across multiple requests — useful when the underlying data may change between page fetches. **Required permission:** `Get Prices Data From API - Price Columns (api-prices-extraction)` ```http theme={null} POST {api_url}/api/prices/{priceColumn} Content-Type: application/json Authorization: Bearer {token} // or ApiKey client_id: {unique client id} // when using ApiKey, client_id is not needed ``` | Path parameter | Description | | --------------- | ---------------------------------------------------------------------------------------- | | `{priceColumn}` | The `name` value of a price column returned by the GET endpoint (e.g. `LocalListPrice`). | ## Request body ```json theme={null} { "Page": 1, "Skip": 0, "NrOfRecords": 100, "Fields": ["cd_ItemNumber", "Price"], "OrderByNew": [ { "columnName": "Price", "type": "desc" } ], "Filters": [ { "columnName": "Price", "op": "greaterThan", "value": "100" } ], "UseCache": false, "CacheKey": null } ``` ## Request body parameters ### Pagination | Field | Type | Default | Description | | ------------- | --------- | ------- | ---------------------------------------------------------------------------------------------------------------------------------------------- | | `Page` | `integer` | `1` | The page number to fetch (1-based). Used together with `NrOfRecords`. When using a `CacheKey`, this controls which pre-built page is returned. | | `Skip` | `integer` | `0` | Number of records to skip before applying paging. Useful for offset-based access. | | `NrOfRecords` | `integer` | `100` | Number of records per page. | ### Field selection | Field | Type | Default | Description | | -------- | ---------- | ------------------- | --------------------------------------------------------------------------------------------------------------------- | | `Fields` | `string[]` | `null` (all fields) | Limits the response to the specified column names. Use the `fields` array from the GET endpoint to find valid values. | ### Sorting | Field | Type | Default | Description | | ------------ | ---------- | ------- | ------------------------------------------------------------------------------------------------------------------------------------------------ | | `OrderBy` | `string[]` | `null` | Simple ascending sort by one or more column names. | | `OrderByNew` | `object[]` | `null` | Advanced sorting. Each entry has `columnName` (string) and `type` (`"asc"` or `"desc"`). Takes precedence over `OrderBy` when both are provided. | ### Filtering | Field | Type | Default | Description | | --------- | ---------- | ------- | ------------------------------------------------------------------------------------- | | `Filters` | `object[]` | `null` | Array of filter conditions to apply. See [Filter structure](#filter-structure) below. | ### Caching | Field | Type | Default | Description | | ---------- | --------- | ------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `UseCache` | `boolean` | `false` | When `true`, the API builds a snapshot that freezes the set of matching rows and their order based on your sorting. Returns the first page immediately. Use the returned `CacheKey` for subsequent page requests to walk through the same stable row set. Column values still reflect the latest data at the time each page is fetched. | | `CacheKey` | `guid` | `null` | When provided, fetches the specified page from an already-built snapshot. Combine with `Page` to walk through pages. When `CacheKey` is set, `UseCache` is not required. | ## Filter structure Each filter object in the `Filters` array can have the following fields: | Field | Type | Description | | ------------ | ---------- | --------------------------------------------------------------------------------------------------------------- | | `columnName` | `string` | The column to filter on. Must be a field listed under `fields` in the GET response. | | `op` | `string` | The filter operator. See supported operators below. | | `value` | `string` | The filter value as a string. For `Between` / `NotBetween`, use `"low\|high"` format. | | `valueArray` | `string[]` | For multi-value operators (`ContainsAny`, `NotContainsAny`). Supply the list of values here instead of `value`. | ### Supported operators (`op`) | Operator | Description | | -------------------- | ------------------------------------------------------------- | | `=` | Equals. Empty string matches null or empty values. | | `!=` | Not equals. Empty string matches not-null / not-empty values. | | `equals` | Numeric equals. | | `notEqual` | Numeric not equal. | | `greaterThan` | Greater than. | | `greaterThanOrEqual` | Greater than or equal. | | `lessThan` | Less than. | | `lessThanOrEqual` | Less than or equal. | | `Between` | Between two values. Use `value` in `"low\|high"` format. | | `NotBetween` | Not between two values. Use `value` in `"low\|high"` format. | | `Contains` | Substring match (LIKE `%value%`). | | `NotContains` | Does not contain substring. | | `StartsWith` | Starts with the given string. | | `NotStartsWith` | Does not start with the given string. | | `EndsWith` | Ends with the given string. | | `NotEndsWith` | Does not end with the given string. | | `ContainsAny` | Matches any value in `valueArray`. | | `NotContainsAny` | Does not match any value in `valueArray`. | | `equalsBlank` | Value is null or empty. | | `notEqualBlank` | Value is not null and not empty. | ## Cache-backed pagination When fetching large datasets where you need to page through results reliably — especially if the underlying data could change between requests — use the cache snapshot flow: 1. **Build the snapshot** — Send a POST request with `UseCache: true` and your desired `OrderBy` / `OrderByNew` and `NrOfRecords`. The response metadata includes: * `CacheKey` — a GUID identifying this snapshot * `TotalRows` — total number of rows in the snapshot * `TotalPages` — total number of pages at the chosen page size * `PageSize` — confirmed rows per page * The first page of data 2. **Fetch subsequent pages** — Send additional POST requests with the same `CacheKey` and increment `Page` (e.g. `Page: 2`, `Page: 3`, …). Ordering and page size are already locked into the snapshot, so you do not need to resend them. This guarantees that every matching record appears exactly once across pages and that ordering is stable, regardless of inserts or deletes in the source table while you are paging. Column values are still read fresh on each page fetch. Snapshots are automatically cleaned up after 24 hours. Page through the snapshot promptly after receiving the cache key. You may include filters when fetching pages. These filters are applied after the snapshot has been created, when the page is read. If you use filters, use the same filters for every page request to keep the result consistent. ### Example — page 1 (build cache) ```http theme={null} POST {api_url}/api/prices/LocalListPrice Content-Type: application/json Authorization: Bearer {token} { "UseCache": true, "NrOfRecords": 500, "OrderByNew": [{ "columnName": "cd_ItemNumber", "type": "asc" }] } ``` ### Example — page 2 (use existing cache) ```http theme={null} POST {api_url}/api/prices/LocalListPrice Content-Type: application/json Authorization: Bearer {token} { "CacheKey": "3fa85f64-5717-4562-b3fc-2c963f66afa6", "Page": 2 } ``` # List price columns Source: https://developer.priceedge.com/api-reference/price-extraction/list-price-columns Returns all price columns configured in the system along with metadata about their underlying source tables. ## List price columns Returns all price columns configured in the system, along with metadata about their underlying source tables. Use this endpoint to understand which price columns exist, what table each one reads from, and what fields are available for filtering or selecting when you call the extraction endpoint. **Required permission:** `Get Prices Data From API - Price Columns (api-prices-extraction)` ```http theme={null} GET {api_url}/api/prices Content-Type: application/json Authorization: Bearer {token} // or ApiKey client_id: {unique client id} // when using ApiKey, client_id is not needed ``` ## Response fields Each item in the returned array describes one price column: | Field | Type | Description | | ----------------- | ---------- | ----------------------------------------------------------------------------------------------------------------------------- | | `code` | `string` | Unique identifier for the price column. | | `name` | `string` | Display name of the price column. Use this as the `{priceColumn}` path parameter in the POST endpoint. | | `sourceTable` | `string` | The underlying database table that this price column reads from. | | `valueColumnName` | `string` | The specific column in `sourceTable` that holds the price value. | | `priceType` | `string` | The type or category of this price column. | | `fields` | `string[]` | All available columns on `sourceTable`. Use these as valid values for `Fields`, `Filters`, and `OrderBy` in the POST request. | ## Example response ```json theme={null} [ { "code": "LocalListPrice", "name": "LocalListPrice", "sourceTable": "ExtPrices_LocalList", "valueColumnName": "Price", "priceType": "pepe", "fields": ["cd_ItemNumber", "cd_PriceList", "Price", "ValidFrom", "ValidTo"] } ] ``` # Price extraction Source: https://developer.priceedge.com/api-reference/price-extraction/overview Discover price column structure and extract underlying source data with filtering, sorting, pagination, and cache-backed snapshots. ## Overview The Price Extraction endpoints let you discover how price columns are structured and read the underlying source data that drives prices in the system. Together they provide a controlled way to extract price data with support for filtering, sorting, pagination, and cache-backed snapshots that guarantee consistent results across pages. **Required permission:** `Get Prices Data From API - Price Columns (api-prices-extraction)` ## Endpoints | Method | Endpoint | Description | | ------ | ------------------------------------ | ------------------------------------------------------- | | `GET` | `{api_url}/api/prices` | List all price columns and their source table metadata. | | `POST` | `{api_url}/api/prices/{priceColumn}` | Read records from a price column's source table. | ## Typical workflow 1. Call **GET** `/api/prices` to discover available price columns, source tables, and filterable fields. 2. Call **POST** `/api/prices/{priceColumn}` with filters, field selection, and sorting to extract data. 3. For large datasets, use **cache-backed pagination** to page through a stable snapshot without missing or duplicating rows. 4. Optionally, configure a **webhook with cache enabled** to receive a `cacheKey` automatically when the source changes. See [Webhook cache snapshot](/api-reference/webhooks/webhook-cache). Discover configured price columns and their source table fields. Read, filter, sort, and page through price source data. Receive a cache key automatically when a webhook fires, then page through results. # Webhook Source: https://developer.priceedge.com/api-reference/webhooks/webhook How to setup webhooks ## What are webhooks It is an event-driven approach that ensures a service provider can send the right notification to the right external services at the time it occurs. In another way, it is a machine to machine architectural pattern that enables a client API to receive updates to data as they happen, rather than polling for the latest updates. ## How it works When an event is fired inside Price Edge that a webhook is registered to, a request will be sent to the registered endpoint with the following signature ```http theme={null} POST {REGISTERED ENDPOINT} HTTP/1.1 x-priceedge-webhook-signature: {SHA512 Signature} content-type: application/json { "eventType": "change", "eventTime": "2023-06-14 15:54:10", "eventId": "3DD64E28-4455-40C9-BE02-DEB7D5E39B76", "source": "table", "sourceObject": "Extension_Factors" } OR { "eventType": "change", "eventTime": "2023-06-14 15:54:10", "eventId": "3DD64E28-4455-40C9-BE02-DEB7D5E39B76", "source": "pepe", "sourceObject": "NextRRP" } OR { "eventType": "change", "eventTime": "2023-06-22 16:59:14", "eventId": "13069F5C-EC73-437F-9842-F51C77CA01C4", "source": "pepe", "sourceObject": "Suggested_Price", "pageNumber": 1, "data": [ { "cd_ItemNumber": "100011", "cd_PriceList": "SE", "Value": 127 }, { "cd_ItemNumber": "100012", "cd_PriceList": "NO", "Value": 209.25 } ] } ``` ## Register a new web hook If you choose "includeData": true, the max repsonse body size is 8000 chars, which can result in invalid JSON if longer `includeData` and `includeDataCacheKey` are mutually exclusive. Use one or the other, not both. ```Http theme={null} PUT {api_url}/api/system/webhooks Content-Type: application/json { "externalUrl": "https://webhook.site/3431a3c0-a123-4e24-9bbc-5ae0acf780d9", "sharedSecret": "bamboo", //any text, it will be used in the signature "eventType":"change", "eventSource":"table", //pepe, table "eventSourceObject":"Item_Extension", //pepe(price)column or table name "includeData": false, //optional; true/false if expeted alot of data then false } ``` For webhooks that deliver a cache snapshot instead of row data, see [Webhook cache snapshot](/api-reference/webhooks/webhook-cache). ## List registered web hooks ```Http theme={null} GET {api_url}/api/system/webhooks ``` ## Unregister web hooks ```Http theme={null} DELETE {api_url}/api/system/webhooks/{ID} ``` ## Test web hooks This will simulate a change in the object it is registered to. It could mean that other hooks registered to the same event will also receive a ping *(this behaviour will change in the future to only send the notification to this specific webhook)* ```Http theme={null} POST {api_url}/api/system/webhooks/{ID} ``` # Webhook cache snapshot Source: https://developer.priceedge.com/api-reference/webhooks/webhook-cache Configure webhooks to automatically build a cache snapshot when they fire, for any event source. ## Cache snapshot Webhooks can be configured to automatically build a cache snapshot when they fire, so your integration receives everything it needs to begin paging immediately — without building the cache manually. This works for **any webhook source**, including tables (`table`) and price columns (`pepe`). ## How it works When **`includeDataCacheKey`** is enabled on a webhook, each time the webhook fires it: 1. Builds a snapshot that freezes the **set of matching rows and their order** for the configured source object. 2. Delivers a webhook callback with the cache metadata instead of raw row data. When you subsequently fetch pages using the `cacheKey`, the row selection and ordering are stable, but the column values returned reflect the current state of the data at the time each page is fetched. **`includeData`** and **`includeDataCacheKey`** are mutually exclusive — you can use one or the other, not both. ## Webhook notification payload In addition to the standard webhook fields (`eventType`, `eventTime`, `eventId`, `source`, `sourceObject`), the body includes: | Field | Type | Description | | ------------ | --------------- | ------------------------------------------------------------------------------------- | | `cacheKey` | `string` (GUID) | Identifier for the snapshot. Pass this as `CacheKey` in POST requests to fetch pages. | | `totalRows` | `integer` | Total number of rows in the snapshot. | | `totalPages` | `integer` | Number of pages at the configured page size. | | `pageSize` | `integer` | Rows per page for this snapshot. | Use `source` and `sourceObject` from the payload to determine which data endpoint to call when fetching pages. ### Example payload — table source ```json theme={null} { "eventType": "change", "eventTime": "2023-06-14 15:54:10", "eventId": "3DD64E28-4455-40C9-BE02-DEB7D5E39B76", "source": "table", "sourceObject": "Item_Extension", "cacheKey": "3fa85f64-5717-4562-b3fc-2c963f66afa6", "totalRows": 8400, "totalPages": 17, "pageSize": 500 } ``` ### Example payload — price column source ```json theme={null} { "eventType": "change", "eventTime": "2023-06-14 15:54:10", "eventId": "3DD64E28-4455-40C9-BE02-DEB7D5E39B76", "source": "pepe", "sourceObject": "LocalListPrice", "cacheKey": "3fa85f64-5717-4562-b3fc-2c963f66afa6", "totalRows": 12500, "totalPages": 25, "pageSize": 500 } ``` ## Fetching data after a webhook fires Use `cacheKey` from the webhook payload and call the appropriate data endpoint based on `source`: ### Table source ```http theme={null} POST {api_url}/api/tables/{sourceObject} Content-Type: application/json Authorization: Bearer {token} { "CacheKey": "", "Page": 1 } ``` See [Get data](/api-reference/endpoint/GET-DATA) for the table data endpoint. ### Price column source ```http theme={null} POST {api_url}/api/prices/{sourceObject} Content-Type: application/json Authorization: Bearer {token} { "CacheKey": "", "Page": 1 } ``` See [Extract prices](/api-reference/price-extraction/extract-prices) for cache-backed pagination details. Increment `Page` for each subsequent page until you have consumed `totalPages` pages. ## Webhook configuration settings | Setting | Description | | -------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | **`includeDataCacheKey`** | Enables the snapshot behavior. When `true`, each trigger builds a snapshot and sends cache metadata in the callback instead of row data. | | **Cache page size** | Number of rows per page in the snapshot. Accepts values from 1 to 100,000. Defaults to 100 when not set. | | **Immediate delivery** | When on, the snapshot is built and delivered on every trigger immediately. When off, delivery is deferred until the configured quiet period has elapsed since the last activity. | | **Quiet period (minutes)** | Minutes of inactivity that must pass before a deferred snapshot is built and delivered. Only applies when immediate delivery is disabled. | Snapshots expire after approximately 24 hours. Ensure your integration pages through the full snapshot shortly after receiving the webhook. ## Register a webhook with cache ### Table source example ```http theme={null} PUT {api_url}/api/system/webhooks Content-Type: application/json { "externalUrl": "https://your-endpoint.example.com/webhook", "sharedSecret": "your-secret", "eventType": "change", "eventSource": "table", "eventSourceObject": "Item_Extension", "includeDataCacheKey": true, "cachePageSize": 500, "immediateDelivery": true } ``` ### Price column source example ```http theme={null} PUT {api_url}/api/system/webhooks Content-Type: application/json { "externalUrl": "https://your-endpoint.example.com/webhook", "sharedSecret": "your-secret", "eventType": "change", "eventSource": "pepe", "eventSourceObject": "LocalListPrice", "includeDataCacheKey": true, "cachePageSize": 500, "immediateDelivery": true } ``` # Webhook Security Source: https://developer.priceedge.com/api-reference/webhooks/webhook-security ## Security By default, Price Edge will not send any kind of data through the web hooks. The reason for that is to ensure that only authorised entities have access to the data. When a request is received, it is the client that needs to request the data using the regular flow. If however you do want to include the data in the request, you can set this when registering the webhook. The receiving service can implement a verification step as Price Edge will include a hashed signature in the request header (`X-Priceedge-Webhook-Signature`), which uses the shared secret value that was sent when the hook was registered. The signature text is formatted as: `"{data.eventTime}.{data.eventId}.{clientSecret}”` (data is the body of the request). A sample C# program to check the signature is included bellow. One additional validation the receiving service can implement is only allowing the Price Edge IP (`thirty-four.242.194.17`) to call the endpoint. ```csharp theme={null} using System; using System.Security.Cryptography; using System.Text; public class Program { private static byte[] ComputeSHA512Hash(string data) { byte[] hash; using (SHA512 shaM = SHA512.Create()) { var b = Encoding.UTF8.GetBytes(data); hash = shaM.ComputeHash(b); } return hash; } private static bool check(string serverSignature, dynamic data){ var clientSecret = "THIS IS A SECRET"; var stringToHash = $"{data.eventTime}.{data.eventId}.{clientSecret}"; var hmacResult = ComputeSHA512Hash(stringToHash); var clientSignature = Convert.ToHexString(hmacResult); return clientSignature == serverSignature; } public static void Main() { var requestBody = @"{ ""eventType"": ""change"", ""eventTime"": ""2023-06-14 13:56:12"", ""eventId"": ""1A2AE4F6-60EE-49BC-B1E5-BCCD4D77EA6D"", ""source"": ""table"", ""sourceObject"": ""Extension_Factors"" }"; dynamic body = Newtonsoft.Json.JsonConvert.DeserializeObject(requestBody); if ( check("3A003D142EB816099406B89D41DAA6F060885320BB9D4C8288CE46F7A78DB859A99AD34766A23476C3AB81A680997811D7FF2F15AF37DD971FEB389F063B938E", //this is what you need to check //Take from request header "X-Priceedge-Webhook-Signature" body)) Console.WriteLine("Good signature"); else Console.WriteLine("Bad signature"); } } ``` # Introduction Source: https://developer.priceedge.com/index Integrate PriceEdge with your applications and data pipelines. Learn how to authenticate, read and update table data, and receive real-time updates through webhooks. ## Key Concepts Connect to the PriceEdge API, manage table data, and subscribe to change events in real time. Make your first authenticated request. Retrieve, filter, create, and update records in PriceEdge tables. Subscribe to PriceEdge events and securely process change notifications. Retrieve pricing results from PriceEdge for use in downstream systems, reports, and automated workflows.