Webhooks

When a payer, a subscription, a receivable, a transaction or a payment session is created or changes, Repejo sends an event to your system. The event is a signed JSON POST to each active endpoint that subscribes to it. Repejo counts the delivery as successful when your endpoint answers with HTTP 2xx.

Set up an endpoint in Repejo

An endpoint is the HTTPS URL in your system that receives the events. Log in to Repejo as an administrator and do these steps:

  1. Click Settings (Inställningar) in the top menu.
  2. Click Webhooks in the left menu.
  3. Click Add webhook.
  4. In URL, type the HTTPS URL of your endpoint.
  5. In Description, type a name that tells what system receives the events.
  6. Keep Send failure alerts selected. Repejo then sends an email when deliveries to the endpoint fail, at most once a day. The email goes to the users who have Receive error alerts (Få felaviseringar) turned on in their user settings.
  7. Under Subscribed Events, select the events that your system needs.
  8. Click Create.
  9. In the list of endpoints, click Show next to Secret.
  10. Copy the secret key to your endpoint. Your endpoint needs it to make sure that the signature is correct.

Each endpoint gets its own secret key. If you have one endpoint in the test environment and one in production, they have different keys.

See the deliveries

  1. Click Settings, then Webhooks.
  2. Click Deliveries (Leveranser) in the left menu. Each row shows the event, the status and the HTTP response of your endpoint.
  3. To see the JSON that Repejo sent, click View payload.
  4. To send an event again, for example after you fixed your endpoint, click Resend.

Available events

type EventType =
| "payer.created"
| "payer.updated"
| "subscription.created"
| "subscription.updated"
| "transaction.created"
| "transaction.updated"
| "receivable.created"
| "receivable.updated"
| "payment_session.created"
| "payment_session.succeeded"
| "payment_session.finalized"
| "payment_session.cancelled";

Repejo sends the payment_session.* events for Payment Sessions ("Pay by Link"). The other events describe what happens to the donor, the mandate and each charge.

Both families fire for a completed recurring mandate

One completed Payment Session sends events from both families, in this order:

payment_session.created     // session created (donor not yet finished)
payer.updated               // Autogiro: mandate registered on the donor
payment_session.succeeded   // recurring mandate signed
payer.updated               // donor details from the checkout (skipped when unchanged)
subscription.created        // the resulting subscription/mandate (data.id = sub_…)
payment_session.finalized   // post-processing done; mandate documents available

The payer.* events depend on the payment method and on what the donor changed, so don't rely on their position or count. The API creates the payer when you create the session, so it sends payer.updated, not payer.created.

To match a Payment Session with your own records, use payment_session.succeeded. It carries the metadata you sent on POST /payment_session. Subscribe to the events you need, from one family or from both, and use X-Repejo-Event-ID to find duplicates. If you need the signed mandate PDF or the BankID signature XML, wait for payment_session.finalized. Repejo sends it once for each succeeded session, when the documents are ready, with their download urls in data.documents.

Payload

type WebhookPayload = {
sent_at: string;
event_type: EventType;
data: Payer | Subscription | Receivable | Transaction | PaymentSession;
};

amount is a string, for example "150", not a number, in both payment_session.* and subscription.* payloads.

A payment_session.succeeded payload contains the resulting subscription, payment_method (object) and payer under data (the same shapes the subscription.* and payer payloads carry), so you can match the subscription and the payer without another API call. payment_session.finalized adds documents. They are null for a one-time session, which has no subscription; the payer is always present. See the Payment Sessions reference for a full example.

Payloads can contain personal data

subscription.created.data.payment_method for an Autogiro mandate contains personal data: personal_identity_number (Swedish personnummer), clearing_number, account_number and payer_number.

Make sure that the signature is correct before you parse a payload. Store the payloads safely, and give access to the personal data only to the people who need it.

Embedded relations

  • Subscription events always embed the related payer as a nested object (the same fields the payer payload carries), in addition to payer_id. The field is null when no payer is linked or the payer can no longer be loaded (for example, it was deleted).
  • Subscription events embed the related payment_method as a nested object with type-specific fields (see PaymentMethod). The field is null when no payment method is linked.
  • Transaction and receivable events always embed the linked payer as a nested object (same fields as the subscription payer). The field is null when no payer is linked or the payer can no longer be loaded (for example, it was deleted).
  • Transaction events carry subscription_id, the subscription of the transaction's receivable, or null when the receivable has none. A one-time gift can carry one too, so read type ("recurring" or "onetime") to tell a monthly charge from a one-time payment.
  • Each month of a recurring gift sends receivable.created (unpaid, or rejected when no Autogiro payment order stands behind it) and, once paid, receivable.updated. A card, Apple Pay, Google Pay or Swish charge sends transaction.created with status pending when it starts and transaction.updated when the outcome is known. An Autogiro charge sends one transaction.created with the outcome from the bank's file. Events can reach you out of order, because a failed delivery is retried later. A transaction never goes back to pending, so ignore a pending event for a transaction you already have with another status.
  • Receivable events always include external_transaction_id and paid_at. external_transaction_id holds the external id when the receivable has a successful transaction with one — Adyen's pspReference for an Adyen payment — and is null otherwise. paid_at is the ISO 8601 UTC timestamp of the payment (exact for Swish, card, Apple Pay, Google Pay and invoice; 12:00 Europe/Stockholm on the paid date for Autogiro, Bankgiro, Stripe subscriptions and offline payments) and null for unpaid receivables. See the Receivable payload for details.
  • On an Adyen recurring gift the donor pays the first period in the checkout, so the first period is already paid when subscription.created arrives. The signing sends a paid receivable.created and a transaction.created dated the signing day. If your organisation charges later periods itself (collected_by is "organisation"), your first charge is for the second period. Do not charge the first period again.
  • payment_session.succeeded embeds the resulting subscription, the signed payment_method object (same type-specific fields as the subscription payment method) and the payer, plus payment_channel, collected_by and the deprecated payment_method_type, which replace the scalar preselect payment_method. For a one-time session (no subscription) all of these except the payer are null; the payer is always present.
  • data.request_type echoes what the session was created for, on all four payment_session.* events: subscription or payment_method. A payment_method session sets up an Autogiro mandate alone, so its subscription, amount and currency are null while payment_method and payer are populated, and its documents carry only the signature XML.
  • payment_session.finalized has the same shape as succeeded plus a documents object. It is sent for every succeeded session once post-processing has finished (for a one-time payment, straight after succeeded), whether or not any document exists.
  • Subscription events and payment_session.finalized carry a documents object with mandate_pdf_masked_url, mandate_pdf_unmasked_url and signature_xml_url. Each is an authenticated API url (send your Bearer token) or null when that document does not exist at the time of the event: PDF urls appear once the mandate PDF has been rendered (right after signing for Autogiro and SEPA Direct Debit mandates on a checkout that enables the mandate language, Swedish / German); the XML url exists only for mandates signed with BankID. See Payment Sessions for the endpoints.
  • Payer events are sent without extra relations unless something else has been added to the entity before publication.

Headers

Content-Type: application/json
User-Agent: Repejo-Webhooks/1.0
X-Repejo-Delivery-ID: <delivery_id>
X-Repejo-Event-ID: <event_id>
X-Repejo-Event-Type: <event_type>
X-Repejo-Attempt: <attempt>
Repejo-Signature: sha256=<hex>

Signature

Repejo-Signature is sha256=<hex>: an HMAC-SHA256 of the raw JSON body, made with the secret key of the endpoint.

Compute it over the raw body you received, not over JSON that you serialized again.

Retries

When a delivery fails, Repejo tries again and waits longer each time. It makes up to 20 attempts over about twelve days.

Your endpoint must respond within 30 seconds. Repejo counts a slower response as a failed delivery and tries again later. Accept the event, for example by putting it in a queue, respond, and process it after that.

If the last attempt also fails, Repejo stops and marks the delivery as abandoned.

Tutorial: Autogiro mandates managed outside Repejo

Use this flow when Repejo collects and signs the Autogiro mandate, but your own CRM or backend talks to Bankgirocentralen and charges the donor. For example, Repejo runs the face-to-face or website checkout, and your system sets up the Autogiro payments.

1. Create an endpoint

Create an HTTPS endpoint in your system that accepts POST requests with a raw JSON body. Verify the Repejo-Signature header against the raw body before you parse the payload.

Answer 2xx when you have stored the event. Repejo tries again after any other answer.

2. Set up an endpoint in Repejo

Set up an endpoint in Repejo, as described at the top of this page, and select both subscription.created and subscription.updated.

You need both, because a subscription can change after Repejo creates it.

3. Receive and filter the events

Your endpoint can also get subscriptions with other payment methods. Read payload.data.payment_channel and payload.data.collected_by and skip the events that you do not handle. An Autogiro subscription on your own bankgiro has payment_channel "autogiro" and collected_by "organisation".

You can get the same subscription more than once: when it changes again, and when Repejo retries a failed delivery. Make your handler safe to run twice.

  • Use X-Repejo-Event-ID to find a retry of an event that you already have.
  • You can also resend a delivery from the Repejo back office. A resend keeps the X-Repejo-Event-ID and gets a new X-Repejo-Delivery-ID. The automatic retries of one delivery keep its delivery id (only X-Repejo-Attempt changes). If a resend must be ignored, find duplicates by event id. If it must be processed again, use the delivery id.
  • Use data.id as the id of the subscription in your CRM. It does not change.
  • Update the record if it exists and create it if it does not, so that a repeated subscription.updated does not create a second mandate or payment instruction.

4. Add custom attributes

To link a subscription to a campaign or a source in your CRM, create a custom attribute in Repejo, for example a campaign id that points to a Salesforce campaign.

Repejo sends the value in payload.data.metadata of the subscription, so you can save it on the record in your CRM.

metadata always contains every defined custom attribute. An attribute without a value is an empty string "", so every delivery has the same keys.

Campaign tags in the link to the checkout need no custom attribute. Every utm_* parameter and the ad click ids fbclid, gclid, gbraid, wbraid and msclkid are in payload.data.tracking on payer, subscription and receivable events. Repejo keeps at most 10 tags, drops keys longer than 50 characters and cuts values to 200 characters.

5. Test the flow

The test environment fakes every payment method, so you can test the whole flow there. Set up a payment method in the test environment, create a checkout that uses it, and give a recurring gift.

Make sure that your endpoint gets subscription.created and, when applicable, subscription.updated with the expected payment_channel, collected_by and metadata values.