Delivery and security
Which headers arrive, how your system verifies the signature, what it must respond and what happens during an outage.
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:
| Header | Content | Example |
|---|---|---|
webhook-id | identifier of the message, stays the same on retries | msg_2KWPBgLlAfxdpx2AI54pPJ85f4W |
webhook-timestamp | time of the delivery attempt in seconds since 1970 (Unix time) | 1674087231 |
webhook-signature | one or more signatures, separated by spaces | v1,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.
- Read the secret. The secret has the form
whsec_<Base64>. The part afterwhsec_is the key in Base64, 32 bytes long. Each endpoint has a secret of its own. - 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.
- Build the string.
webhook-id, dot,webhook-timestamp, dot, body:msg_2KWP….1674087231.{"type":"tender.published",…}. - Compute the checksum. HMAC-SHA256 over this string with the key, result in Base64, prefixed with
v1,. - Compare. If one of the entries in
webhook-signaturematches exactly, the message is genuine. Compare in constant time so that the run time reveals nothing about the signature. - Check the time. If
webhook-timestampis 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.
| Language | Library |
|---|---|
| Python | standardwebhooks on PyPI |
| JavaScript and TypeScript | standardwebhooks on npm |
| Java and Kotlin | com.standardwebhooks:standardwebhooks on Maven Central |
| Go | Go module github.com/standard-webhooks/standard-webhooks/libraries/go |
| Rust | standardwebhooks on crates.io |
| Ruby | standardwebhooks on RubyGems |
| C# | StandardWebhooks.StandardWebhooks on NuGet |
| PHP | in the repository standard-webhooks/standard-webhooks |
| Elixir | in 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 response | What procuris does |
|---|---|
| 2xx | delivered, done |
| 3xx | failure. procuris follows no redirect. The owner enters the new address under Webhooks. |
| 410 Gone | no further messages to this address until the owner chooses Resume delivery under Webhooks |
| 429, 502, 504 | failure. 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-After | the 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, other | failure, 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.
| Attempt | Interval to the previous one | Time since the first attempt (h:min:s) |
|---|---|---|
| 1 | none | 00:00:00 |
| 2 | 5 seconds | 00:00:05 |
| 3 | 5 minutes | 00:05:05 |
| 4 | 30 minutes | 00:35:05 |
| 5 | 2 hours | 02:35:05 |
| 6 | 5 hours | 07:35:05 |
| 7 | 10 hours | 17:35:05 |
| 8 | 14 hours | 31:35:05 |
| 9 | 20 hours | 51:35:05 |
| 10 | 24 hours | 75: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-idand 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 intender.*can only be filled by a system with API access, via the API withupdated_since. Oldersearch.hitcannot 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.
- 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. - 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.
- 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.
- The owner chooses Replace secret now next to the endpoint and passes the new secret to your IT through a password manager.
- 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.
- Your IT enters the new secret and removes the old one.
- 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.
Related pages
Request a quote
Tell us what you want to use or connect. Your quote is based on that scope.