Skip to content

Webhooks Integration Handbook

This handbook walks you through every step of integrating with Tendium using webhooks — from setting up your API endpoint to verifying payload signatures and reporting statuses back to Tendium.

Prerequisites

To set up webhooks in the Tendium platform, you will need:

  • An endpoint that can receive HTTP POST requests
  • Access to the Tendium platform to configure webhooks and generate tokens
  • The ability to send HTTP POST requests to fetch additional information from Tendium

Step 0: Set up your API endpoint

Set up an API endpoint that can receive HTTP POST requests from Tendium.

Step 1: Configure webhooks in Tendium

Create the webhook

Webhooks can be created in the settings page if you have the necessary access. Only company admins can view and set up new webhooks.

  1. Go to https://app.tendium.com/settings/webhooks
  2. Click Create webhook
  3. Enter your Endpoint URL — this must be a valid, publicly available API that can receive HTTP POST requests
  4. Enter a Secret — any string that can be used to verify that the payload is coming from Tendium and has not been tampered with

Create an integration token

To receive additional information (e.g., bid data) after receiving the initial webhook payload, you will need to make requests to Tendium’s public API using an integration token.

  1. Go to https://app.tendium.com/settings/tokens
  2. Click Create an integration token
  3. Store the token in a safe place — you will only be able to see it once

Triggering the webhook

Currently, Tendium only supports webhooks for creating bids. You can trigger the webhook by:

  • Adding a Tender, Call-Off to a bid space (automatic)
  • Manually creating the bid
  • Pressing the send webhook button on an existing bid

In your bid space, you can configure if you want the webhooks to be sent automatically when you create bids.

Step 1.1: Testing the webhook

Initiate the webhook by creating a bid. If the bid space has automatic webhooks turned on, an HTTP POST request will be sent to the specified URL upon creating the bid.

Alternatively, you can manually trigger the webhook by pressing the send webhook button on the bid.

Example payload

{
"eventType": "BidCreated",
"data": {
"bidId": "<bidId>",
"itemId": "<itemId>",
"bid": "query{webhookGetBid(input:\"<bidId>\"){id item{id name description specialData{buyerInformation{org orgId orgName}deadline contractDuration contractValue{amount currency}linkForSubmittingTender}itemType}files manualFiles assignedTo customFields{name type value{...on CustomBidFieldStringValue{string}...on CustomBidFieldArrayValue{unit array}...on CustomBidFieldNumberValue{number unit}...on CustomBidFieldUrlValue{title url}...on CustomBidFieldRangeValue{unit from to}...on CustomBidFieldDateValue{date}...on CustomBidFieldBooleanValue{boolean}...on CustomBidFieldMoneyValue{amount currency}...on CustomBidFieldDateRangeValue{from to}...on CustomBidFieldMoneyRangeValue{from to currency}}}}}",
"publicUrl": "https://prod.public-gateway.radon.tendium.net/graphql",
"bidDetailsPageUrl": "https://app.tendium.com/tender/<itemId>"
}
}

Manual testing (optional)

You can test your integration without using the Tendium platform by sending an HTTP POST request that mimics the webhook payload.

Terminal window
curl -X POST <your-webhook-endpoint-url> \
-H "Content-Type: application/json" \
-d '{
"eventType": "BidCreated",
"data": {
"bidId": "<bidId>",
"itemId": "<itemId>",
"bid": "query{webhookGetBid(input:\"<bidId>\")...}",
"publicUrl": "https://prod.public-gateway.radon.tendium.net/graphql",
"bidDetailsPageUrl": "https://app.tendium.com/tender/<itemId>"
}
}'

Step 2: Fetching the bid data

The initial webhook payload only contains metadata about the bid event — not the full bid data. You need one additional step to get the actual bid data.

  1. Take the string value from data.bid (replace <bidId> with your actual bidId).
  2. Send the payload to the URL provided in data.publicUrl (i.e., https://prod.public-gateway.radon.tendium.net/graphql) under a query property.
  3. Provide the integration token in an Authorization header to authenticate the request.

Full example using cURL

Replace <integrationToken> with your actual integration token, and <bidId> with your actual bidId.

Terminal window
curl --location 'https://prod.public-gateway.radon.tendium.net/graphql' \
--header 'Authorization: Bearer <integrationToken>' \
--header 'Content-type: application/json' \
--data '{ "query": "query{webhookGetBid(input:\"<bidId>\"){id item{id name description specialData{buyerInformation{orgId orgName}deadline contractDuration contractValue{amount currency}linkForSubmittingTender}itemType}files manualFiles assignedTo customFields{name type value{...on CustomBidFieldStringValue{string}...on CustomBidFieldArrayValue{unit array}...on CustomBidFieldNumberValue{number unit}...on CustomBidFieldUrlValue{title url}...on CustomBidFieldRangeValue{unit from to}...on CustomBidFieldDateValue{date}...on CustomBidFieldBooleanValue{boolean}...on CustomBidFieldMoneyValue{amount currency}...on CustomBidFieldDateRangeValue{from to}...on CustomBidFieldMoneyRangeValue{from to currency}}}}}"}'

Example response

{
"data": {
"webhookGetBid": {
"id": "<bidId>",
"item": {
"id": "string",
"name": "string",
"description": "string",
"specialData": {
"buyerInformation": {
"orgId": "string",
"orgName": "string"
},
"deadline": "<number>",
"contractDuration": "string",
"contractValue": { "amount": "<number>", "currency": "string" },
"linkForSubmittingTender": "string"
},
"itemType": "string"
},
"files": [
"https://example-s3.com/signed-url-file1.pdf",
"https://example-s3.com/signed-url-file2.pdf"
],
"manualFiles": [
"https://example-s3.com/signed-url-manual-file1.pdf"
],
"assignedTo": "string",
"customFields": [
{ "name": "string", "type": "Date", "value": { "date": "<number>" } },
{ "name": "string", "type": "Number", "value": { "number": "<number>", "unit": "string" } },
{ "name": "string", "type": "String", "value": { "string": "string" } },
{ "name": "string", "type": "DateRange", "value": { "from": "string", "to": "string" } },
{ "name": "string", "type": "Money", "value": { "amount": "<number>", "currency": "string" } },
{ "name": "string", "type": "URL", "value": { "title": "string", "url": "string" } }
]
}
}
}

Step 2.1: Files (optional)

If you have access to the files feature, the bid payload will include two additional fields:

FieldDescription
filesPublic procurement files
manualFilesFiles uploaded to the platform for the bid

To download the actual files, make an HTTP GET request to the URLs provided. These URLs expire 24 hours after triggering the webhook — make sure to handle them within this timeframe.

Step 2.2: Custom fields

Bids may contain custom fields defined by the customer. These fields are returned as an array under the customFields property in the bid payload.

Each custom field has a name, type, and a value object. The name and type values are set when the customer defines the custom fields in the settings page.

Custom field value schema

Typevalue schema
String{ string: string | null }
Number{ number: number | null, unit: string | null }
Date{ date: number | null }
DateRange{ from: string | null, to: string | null }
Money{ amount: number | null, currency: string | null }
URL{ title: string | null, uri: string | null }

Step 3: Verifying payload signature (optional)

For additional security, a signature header is included in the initial webhook payload. This signature can be verified to make sure that the payload has not been tampered with.

Algorithm: HMAC-SHA512

Header format:

Tendium-Signature: 'signature=<hmac-sha512>,timestamp=<timestamp>'

The signature is computed using the secret value set in the creation of the webhook in the platform, together with the current timestamp and the request body:

hmac-sha512(secret, '<timestamp>.<requestBody>')

Format: hex

Verification steps

  1. Extract the Tendium-Signature header from the webhook request.
  2. Split the header by , to separate key-value pairs, then split each by = to get signature and timestamp.
  3. Concatenate the timestamp and stringified request body: <timestamp>.<stringifiedRequestBody>
  4. Compute the HMAC-SHA512 hex signature using the secret and the value from step 3.
  5. Compare the computed signature with the one extracted from the header in step 2.

JavaScript example

createHmac("sha512", "TestSecret")
.update("<timestamp>.<stringifiedRequestBody>")
.digest("hex");

Step 4: CRM or other integrations (optional)

After receiving the webhook data from Tendium, you can send relevant data points to any CRM you are currently using. Tendium does not currently provide any out-of-the-box integrations, which means you will have to implement this in your solution.

Step 5: Webhook statuses (optional)

You can update the webhook status in the Tendium platform to give users information on whether the webhook payload has been successfully processed outside of Tendium.

The update webhook status request is a GraphQL mutation:

updateWebhookStatus mutation

Arguments:

ParameterTypeRequiredDescription
signatureStringYesThe value provided in Tendium-Signature header. Example: 'signature=abc,timestamp=123'
relatedEntityIdStringYesThe relational ID for which the webhook is created. For BidCreated event type, this represents data.bidId.
statusSendWebhookStatus!YesThe status of the webhook. Supported values: Unknown, Failed, Success.
messageStringNoA custom message the end user will see when hovering over the status icon.

cURL example

Terminal window
curl --request POST \
--header 'content-type: application/json' \
--header 'Authorization: Bearer <token>' \
--url https://prod.public-gateway.radon.tendium.net/graphql \
--data '{"query":"mutation updateWebhookStatus {\n updateWebhookStatus(input: { status: <status>, message: \"<message>\", relatedEntityId: \"<bidId>\", signature: \"<Tendium-Signature>\" }) {\n changedAt\n status\n }\n}","variables":{}}'

GraphQL example

mutation updateWebhookStatus($input: UpdateWebhookStatusInput!) {
updateWebhookStatus(input: $input) {
changedAt
eventType
message
status
}
}

Variables:

{
"input": {
"message": "<message>",
"relatedEntityId": "<bidId>",
"signature": "<signature>",
"status": "<status>"
}
}

Response:

{
"data": {
"updateWebhookStatus": {
"changedAt": "<DateTimeISO>",
"eventType": "<EventType>",
"message": "<message>",
"status": "<status>"
}
}
}

Step 6: Error handling (optional)

GraphQL APIs can return 200 OK response codes for errors, where REST typically produces 4xx and 5xx. These error codes can still occur and are often related to other unexpected issues like network problems.

Check for these properties in the response object for potential errors:

PropertyDescription
errorsArray of all errors returned
errors[n].messageDetails about the error
errors[n].extensionsAdditional information about the error(s)