Integrating
Certification
What has to be true before you can create production keys — and how little of it you have to prove yourself. It is certified per vertical: hotels, flights, transfers and eSIM are reviewed separately, and passing one opens production keys for that vertical's scopes only. Most of each list we read out of our own request log, two of them we test by firing at your system, and only what happens where we cannot see is left for you to show us.
This applies to API partners only
What passing actually unlocks
Production keys are minted by you, in the portal, on the API keys screen. That button is switched off until a certification has passed. Passing switches it on — and that is the whole of it.
No key is ever created or sent here
vcb_live_ secret in existence was shown exactly once, in your browser, to somebody logged into your portal account. If a message claiming to be from us contains a key, it is not from us.- You certify with a test key. The whole procedure runs against
https://sandbox-api.vacabee.comwith avcb_test_key. Nothing is billed, no supplier is contacted, and your ledger balance is untouched — see the sandbox page. - It does not expire. A pass stands until somebody withdraws it. There is no renewal date to diarise and no anniversary on which your access quietly stops — an expiry is an outage scheduled in advance, for a reason unconnected to anything you did.
- Withdrawal never stops your traffic. If a certification is ever withdrawn — by a person, with a written reason — the gate governs ISSUING a key, never USING one. Every production key you already hold keeps working and your customers notice nothing; what you lose is the ability to mint or rotate another one.
- It is certified one vertical at a time. Hotels, flights, transfers and eSIM each have their own list, their own submission and their own verdict. Passing hotels opens
hotels:searchandhotels:bookon a production key and refusesflights:bookby name until flights has passed too — see the next section. - The list narrows by how you sell, not by what you sell. Five of the fifteen entries depend on your settlement mode, and the two webhook probes only appear once you have an endpoint to fire at. Nothing narrows by vertical: each vertical gets the whole list, and it is answered for that vertical — what a traveller was shown before he paid is a different screenshot for a hotel than for an eSIM.
One certification per vertical
There is no single verdict that makes you “certified”. Hotels, flights, transfers and eSIM are certified independently — separate attempts, separate evidence, separate decisions — and each pass unlocks exactly its own scopes on a production key. A key that asks for a scope in a vertical you have not passed is refused when you create it, naming the vertical and what to do about it. Your sandbox keys are never affected: that is where the integration is written, before any of this.
Six scopes belong to no vertical, so they are not certified. They are governed by your account instead, in two groups, and the split is about whose data they reach:
bookings:cancel is not narrowed per vertical
hotels:book there is no hotel booking of yours to cancel.Your account also carries one coarse approval on top of all of this — whether it may have production keys AT ALL. The first vertical you pass grants it. It is necessary and never sufficient: with it and no certified vertical, the only production key you can create is one carrying the three account scopes above.
The full catalogue
Fifteen entries, in three tiers, and EVERY VERTICAL GETS THE WHOLE LIST. The tier is not a label on the work — it is who does it. You will not see all fifteen: five of them depend on how you sell, so a partner who is merchant of record and a partner who uses our hosted checkout get different lists, and the webhook probes only appear once you have an endpoint to fire at. Your own screen shows exactly yours, and nothing else in this procedure is conditional.
The four AUTO entries read the same in every vertical
Two entries can be waived by a reviewer, and only two: R4_SURFACE_COVERAGE and R13_GO_LIVE_PLAN. A waiver always carries a written reason, because a growing list of waivers is visible and a growing silence is not. Everything else cannot be waived at all — including R8_CANCELLATION_TERMS, which is the entry a reviewer would most like to be able to wave through on a Friday and the one whose absence a traveller finds out about at an airport.
What we see ourselves — the four you do not submit
Every call you make against the sandbox is already logged with its route, its status, your idempotency key and — this is the part that makes the rest work — which failure trigger it carried. So four of them are answered from traffic you were going to produce anyway. You do not upload anything for these; you open the screen and they are already ticked, each with the request ids behind it.
A retry with a FRESH key is invisible to us — and it is the one that double-books
R1_IDEMPOTENCY therefore proves that you CAN repeat safely; it cannot prove that your timeout path DOES. Nor does it count bookings: the third signal is "every state-changing request carried a key", not "nothing was bought twice". That one is yours, and the way to test it is sbx_slow plus a client timeout below the response time.You declare the routes. R4_SURFACE_COVERAGE is measured against the list of routes you say you will call in production, not against every operation in the contract. A narrow integration produces a short list and a short list is easy to cover; declaring routes you do not use only makes work for yourself.
The six failure triggers are values of the request header X-Partner-Customer-Ref, and they are the whole reason R2_ERROR_HANDLING is answerable at all. In the sandbox this header is how you ask for a specific failure; a value that does not begin with sbx_ is an ordinary traveller reference and is passed through untouched. Against a vcb_live_ key nothing of this applies — no header makes a live call fail on purpose.
curl -sS -D - "https://sandbox-api.vacabee.com/v1/hotels/bookings" \
-H "Authorization: Bearer $VACABEE_TEST_KEY" \
-H "Idempotency-Key: $(uuidgen)" \
-H "X-Partner-Customer-Ref: sbx_price_changed" \
-H "Content-Type: application/json" \
-d '{
"rateId": "sbx_rate_…",
"externalReference": "booking-2026-0915-abc",
"guests": [ { "firstName": "Ada", "lastName": "Lovelace" } ],
"contact": { "email": "ada@example.com" }
}'
# HTTP/1.1 409 Conflict
# X-Sandbox-Trigger: sbx_price_changed
#
# The header is the proof. R2_ERROR_HANDLING counts a trigger as observed only
# when it is on the response, so a call that did not actually simulate anything
# cannot be mistaken for one that did.- A trigger on the wrong kind of route is announced, not swallowed. Four of the six only make sense where money moves, so on a search or a read they let the call proceed and the response carries
X-Sandbox-Trigger-Ignoredinstead. That header is your signal to move the trigger to a booking call — silence would look like a broken feature. - A typo is refused, loudly. Any value starting with
sbx_that is not one of the six answers400 invalid_requestand lists all six inknownTriggers.sbx_offer_expredwould otherwise behave exactly like a working trigger that decided to do nothing. sbx_slowis the one worth real time. Combine it with a short client timeout and a retry that reuses the same idempotency key: that is the situation — a booking that may or may not have happened — which produces duplicate reservations in integrations that skipped it, and it is what409 idempotency_key_in_progressexists for.
The proof is frozen when you submit
What we test against your system
Webhook receiving is the one thing that is neither visible to us nor genuinely invisible: it happens on your server, but we can knock on it. So we do, rather than asking you to declare it. The failure this catches is not somebody who knows he skipped signature verification — it is an integration that reads the signature header, logs it, and processes the body regardless, run by people who will tell you sincerely that they verify.
We will send you a deliberately wrong signature
R5_WEBHOOK_SIGNATURE check, one delivery arrives with a signature that is correctly shaped, correctly timed and cryptographically WRONG. It is us. Your endpoint is expected to refuse it with any non-2xx. It is sent only to a webhook endpoint registered in the TEST environment — never to a production receiver.- A probe that cannot run is a failure. No endpoint registered, endpoint disabled, DNS does not resolve, TLS handshake fails, connection refused, timeout — all of it fails the entry, and none of it is recorded as skipped. A check that silently did not run looks exactly like a check that passed.
- Every probe is in your own delivery history. Request, response, status and timing are on the delivery like any other, so you can check a finding instead of having to believe it. Signature verification itself is described on the webhooks page.
What you hand in
Six entries are about things we have no way of seeing: your screens, your storage, your people. Each one asks for something specific, and the difference between an accepted answer and a rejected one is almost always whether it names a person and a number instead of a department and an intention.
Files are stored privately and are only ever served through short-lived signed links — never a public URL. The portal names the accepted formats and the size limit next to each entry.
The states, and what each one means
A rejection always names the entry and why
Only one certification is open at a time. A new attempt can be started once the previous one has ended — passed, failed or revoked — because two open submissions could both reach a reviewer and the second one would set an approval the first reviewer never agreed to.
How it runs
- 1Build against the sandbox with a
vcb_test_key, reading this catalogue as you go. The four automatic entries fill themselves in from that traffic. - 2Declare what you are certifying: the verticals you sell, the routes you will call in production, and the webhook endpoint we should probe.
- 3Provoke the six failures and recover from each of them, on the routes you declared. This is the one part that does not happen by itself.
- 4Run the probes from the certification screen and fix anything they find. You can run them as often as you like before submitting.
- 5Answer the six evidence entries and attach what belongs to them. Answers are saved as you go — nobody finishes a catalogue this long in one sitting.
- 6Submit. The catalogue as it reads today and the evidence as the log sees it today are frozen onto the submission, so the review judges what you handed in rather than what either of them has become since.
- 7A reviewer decides each remaining entry and then the submission: passed, sent back for changes, or failed — each with a reason.
- 8Create your production key yourself on the API keys screen. Plan a supervised first day: the response shapes are identical to the sandbox, the behaviour is not.
And then it stands. There is no re-certification on a timer. If your integration changes materially — a new vertical, a change of settlement mode, a rebuild of the checkout — tell your Vacabee contact and we will look at the parts that changed rather than at all of it again. A certification is withdrawn only by a person, only with a written reason, and you are told when it happens.
What the sandbox cannot prove — and what you still need
Three situations cannot be produced in the sandbox at all, because the sandbox deliberately touches no money and no supplier. They are not quietly dropped from the catalogue: they are the legitimate reason to waive R4_SURFACE_COVERAGE, the waiver has to say which of the three it is, and the report prints how many waivers there were.
These belong in the first supervised hours of production, not in the certification. Everything else in the catalogue is reachable before a single euro moves, which is the point: a gate that costs money is a gate that eventually gets skipped.

