procuris

Delivery and security

Which headers arrive, how your system verifies the signature, what it must respond and what happens during an outage.

On request

You receive this service through an individual quote.

Your system processes only messages with a valid signature. procuris signs each message to your endpoint. Signing means putting a signature on it with a secret key that only procuris and your system know. If your system is down for up to three days, no message is lost, because procuris retries failed messages over just over three days. Delivery follows the open Standard Webhooks specification.

What arrives

Each message is a POST request over HTTPS to your address. The body, meaning the content of the request, is JSON (content-type: application/json). Three headers come with it:

HeaderContentExample
webhook-ididentifier of the message, stays the same on retriesmsg_2KWPBgLlAfxdpx2AI54pPJ85f4W
webhook-timestamptime of the delivery attempt in seconds since 1970 (Unix time)1674087231
webhook-signatureone or more signatures, separated by spacesv1,K5oZfzN95Z9UVu1EsfQmfVNQhnkZ2pj9o9NDN/H/pI4=

procuris accepts only HTTPS addresses.

Verify the signature, step by step

The signature is an HMAC-SHA256. In this widespread method, a checksum arises from the message and a secret key. Without the key, it cannot be forged.

The secret is in procuris under Settings › Organization › Webhooks next to the endpoint. Only the person with the role Owner sees the section. It appears only once access has been set up after the contract is signed. Until then, the Webhooks row in the On request section of the same page only requests a quote.

  1. Read the secret. The secret has the form whsec_<Base64>. The part after whsec_ is the key in Base64, 32 bytes long. Each endpoint has a secret of its own.
  2. Read the body raw. Take the body exactly as it arrived, as bytes, before your system parses it as JSON. Even one extra space after parsing and reassembling makes the signature invalid.
  3. Build the string. webhook-id, dot, webhook-timestamp, dot, body: msg_2KWP….1674087231.{"type":"tender.published",…}.
  4. Compute the checksum. HMAC-SHA256 over this string with the key, result in Base64, prefixed with v1,.
  5. Compare. If one of the entries in webhook-signature matches exactly, the message is genuine. Compare in constant time so that the run time reveals nothing about the signature.
  6. Check the time. If webhook-timestamp is more than 5 minutes before or after your clock, discard the message. The limit prevents someone from replaying a captured message later.

Discard incomplete messages as well. This applies if one of the three headers is missing or webhook-timestamp is not an integer. Respond to a discarded message with 401 instead of 2xx, so that procuris counts it as failed and sends it again according to the schedule.

Ready-made libraries

The official libraries of Standard Webhooks replace the six steps. They verify signature and time in one call and evaluate several signatures in the header. An implementation of your own must do that itself during a secret rotation, otherwise it rejects genuine messages during the 24-hour transition.

LanguageLibrary
Pythonstandardwebhooks on PyPI
JavaScript and TypeScriptstandardwebhooks on npm
Java and Kotlincom.standardwebhooks:standardwebhooks on Maven Central
GoGo module github.com/standard-webhooks/standard-webhooks/libraries/go
Ruststandardwebhooks on crates.io
Rubystandardwebhooks on RubyGems
C#StandardWebhooks.StandardWebhooks on NuGet
PHPin the repository standard-webhooks/standard-webhooks
Elixirin the repository standard-webhooks/standard-webhooks

The list comes from the repository standard-webhooks/standard-webhooks. The libraries take the secret with whsec_, the raw body and the three headers.

Responding

Your endpoint responds within 15 seconds with 2xx. That means a status such as 200 or 204. Only then does the message count as delivered.

Your receiver stores first and processes afterwards. It puts the message, for example, into a queue, meaning a list that your system then works through in order. A CRM entry before the response can exceed the 15 seconds. procuris counts that as a failure.

Your responseWhat procuris does
2xxdelivered, done
3xxfailure. procuris follows no redirect. The owner enters the new address under Webhooks.
410 Goneno further messages to this address until the owner chooses Resume delivery under Webhooks
429, 502, 504failure. procuris then sends only one message at a time to this address instead of up to ten, until ten deliveries in a row succeed again.
Header Retry-Afterthe next attempt waits the longer of the two intervals: the one from the schedule or the one from Retry-After, but at most 1 hour from Retry-After
Timeout, connection dropped, otherfailure, next attempt according to the schedule

Backlog behind a proxy. A backlog clears within hours even when throttled. A proxy is a server that passes requests on to your system. If it responds with 502, for example because the service behind it is restarting, procuris sends only one message at a time. With a response time of one second, that is around 3,600 messages per hour. A backlog of a few thousand messages is therefore cleared in one to two hours. After ten successful deliveries in a row, procuris again sends up to ten at once.

If your system is down

An outage of one hour costs no message. procuris attempts a message up to ten times, the last attempt just over three days after the first. If your system is down for one hour, a message from that hour arrives with the third, fourth or fifth attempt. A message from the start of the hour arrives with the fifth attempt, around 2 hours and 35 minutes after its first. One from the end of the hour arrives with the third, just over 5 minutes after its first.

According to the schedule, the last attempt is around 75.6 hours after the first. Each interval is extended by a random share of at most 10 percent so that retries do not arrive at the same time. Before the tenth attempt, that adds up to 2.4 hours. If your system demands longer pauses with Retry-After, the attempts shift accordingly. At the latest 90 hours after the first attempt, a message is delivered or discarded, random share and Retry-After included.

AttemptInterval to the previous oneTime since the first attempt (h:min:s)
1none00:00:00
25 seconds00:00:05
35 minutes00:05:05
430 minutes00:35:05
52 hours02:35:05
65 hours07:35:05
710 hours17:35:05
814 hours31:35:05
920 hours51:35:05
1024 hours75:35:05

For longer disruptions, three rules apply:

  • A single message fails permanently, for example because your system rejects exactly this record. After the tenth attempt, procuris discards it. The person with the role Owner receives a summary email once a day with the webhook-id and event of each discarded message. The remaining messages continue.
  • No delivery to an address succeeds for 5 days. procuris stops delivery to it and writes to the owner. After the response 410, procuris also stops delivery. When your system is ready, the owner chooses Resume delivery next to the endpoint under Webhooks.
  • Catch up on missed messages. The resend reaches back 7 days. After a disruption, the owner resends the missed messages of these 7 days under Webhooks, with their original webhook-id. Older gaps in tender.* can only be filled by a system with API access, via the API with updated_since. Older search.hit cannot be recovered, because the API knows neither Fit nor saved searches.

If nobody with the role Owner can be reached, write to support@procuris.eu. Support then resumes delivery after checking with your organization.

Duplicate messages

The same message can arrive several times, for example when your response gets lost on the way. webhook-id stays the same. Store each processed webhook-id for 4 days and skip a message whose webhook-id you already know. 4 days are enough because procuris delivers or discards a message at the latest 90 hours after its first attempt.

Resent messages can be older than 4 days. For them, comparing the times protects you instead of the identifier. Your system takes over fields from data.tender only if data.tender.lastModified is newer than the stored state. It takes over data.fit from search.hit only if the timestamp of the message is newer than that of the last taken-over Fit, see Events.

Rotate the secret

The owner rotates the secret themselves, in two ways. Under Settings › Organization › Webhooks, each endpoint has Renew secret and Replace secret now. The deciding factor is whether the old secret may remain valid for another 24 hours.

Renew secret

Renew secret rotates the secret without downtime. Take this route for a planned rotation, for example when someone who knows the secret leaves the company. The owner passes the new secret to your IT through a password manager.

  1. From the renewal on, procuris signs each message to this endpoint with the old and the new secret for 24 hours. Both signatures are in the header webhook-signature.
  2. During this time, you enter the new secret into your system. The libraries verify each signature in the header. Your system therefore keeps running without interruption.
  3. After 24 hours, only the new secret is valid.

Replace secret now

Replace secret now is meant for a secret that has become public. The old secret is no longer valid from then on, without a 24-hour transition. Your system, however, keeps verifying with the old one until your IT has entered the new one. Therefore enter it without delay.

  1. The owner chooses Replace secret now next to the endpoint and passes the new secret to your IT through a password manager.
  2. Until it is entered, the messages fail verification. Your receiver responds to them with 401, and procuris delivers them again according to the retry schedule.
  3. Your IT enters the new secret and removes the old one.
  4. The owner checks reception with Send test message.

The retry requires a rejection by your receiver. That means a response other than 2xx to a message with an invalid signature. If it responds with 2xx anyway, as some workflow builders do, the message counts as delivered. procuris then does not retry it. The owner brings such messages back with the resend of the last 7 days. The same applies if entering takes longer than the retry schedule and procuris has discarded the messages.

If nobody with the role Owner can be reached, write to support@procuris.eu.

Identify the sender

The signature proves authenticity, the sender address does not. procuris publishes no fixed sender addresses for firewall rules. Therefore verify authenticity through the signature.

Request a quote

Tell us what you want to use or connect. Your quote is based on that scope.

Request a quote

On this page